Skip to content

Exceptions

The Clio SDK defines a hierarchy of exception classes to help you handle different types of errors appropriately. All exceptions inherit from the base ClioError class.

ClioError
├── ClioAuthError
├── ClioUploadError
├── ClioRateLimitError
└── ClioConfigError

Base exception class for all Clio SDK errors.

from clio.exceptions import ClioError
try:
# SDK operations
await monitor.start_run(context, "Test")
except ClioError as e:
print(f"Clio SDK error: {e}")

All Clio exceptions inherit from this class, so you can catch any SDK-related error by catching ClioError.

Raised when authentication fails or API key issues occur.

Common causes:

  • Invalid API key format
  • Expired or revoked API key
  • Server returns 401 Unauthorized
  • Network issues preventing authentication
from clio.exceptions import ClioAuthError
try:
monitor = ClioMonitor(api_key="invalid_key")
await monitor.start_run(context, "Test")
except ClioAuthError as e:
print(f"Authentication failed: {e}")
# Handle by checking API key, regenerating if needed

Example scenarios:

# Invalid API key format
ClioAuthError("Invalid API key format")
# Server authentication failure
ClioAuthError("Invalid API key")
# Network/server issues
ClioAuthError("Authentication server unavailable")

Raised when file upload operations fail.

Common causes:

  • Network connectivity issues
  • File not found or permissions issues
  • Upload timeout (large files)
  • S3 storage issues
  • Invalid presigned URLs
from clio.exceptions import ClioUploadError
try:
await monitor.start_run(context, "Test")
# ... automation code ...
await context.close() # Upload happens here
except ClioUploadError as e:
print(f"Upload failed: {e}")
# Handle by retrying or checking network connectivity

Example scenarios:

# File not found
ClioUploadError("File not found: /path/to/video.webm")
# Network timeout
ClioUploadError("Upload timed out for large_video.webm")
# Permission issues
ClioUploadError("Failed to upload trace.zip: Permission denied")
# S3 issues
ClioUploadError("Upload failed: Invalid presigned URL")

Raised when API rate limits are exceeded.

Common causes:

  • Monthly upload quota exceeded
  • Too many concurrent requests
  • Server-side rate limiting
  • Bulk upload operations
from clio.exceptions import ClioRateLimitError
try:
await monitor.start_run(context, "Test")
except ClioRateLimitError as e:
print(f"Rate limit exceeded: {e}")
# Handle by waiting or upgrading plan

Example scenarios:

# Monthly quota exceeded
ClioRateLimitError("Monthly rate limit exceeded")
# Too many requests
ClioRateLimitError("Rate limit exceeded, try again later")
# Concurrent upload limits
ClioRateLimitError("Too many concurrent uploads")

Raised when configuration validation fails.

Common causes:

  • Invalid configuration parameters
  • Missing required settings
  • Conflicting configuration options
  • Invalid URL formats
from clio.exceptions import ClioConfigError
try:
config = Config(
api_key="clio_abc123",
base_url="invalid-url"
)
except ClioConfigError as e:
print(f"Configuration error: {e}")
# Handle by fixing configuration

Example scenarios:

# Invalid URL format
ClioConfigError("Invalid base_url format: not-a-url")
# Missing API key
ClioConfigError("API key is required")
# Invalid parameter values
ClioConfigError("retry_attempts must be between 1 and 10")

Handle all possible Clio errors:

from clio import ClioMonitor
from clio.exceptions import (
ClioError,
ClioAuthError,
ClioUploadError,
ClioRateLimitError,
ClioConfigError
)
async def monitored_automation():
try:
monitor = ClioMonitor(api_key=os.getenv("CLIO_API_KEY"))
async with async_playwright() as p:
browser = await p.chromium.launch()
context = await browser.new_context(record_video_dir="./videos")
await monitor.start_run(context, "E2E Test")
# Your automation code here
page = await context.new_page()
await page.goto("https://example.com")
await context.close()
except ClioAuthError as e:
print(f"❌ Authentication failed: {e}")
print("💡 Check your API key and try again")
return False
except ClioRateLimitError as e:
print(f"⏳ Rate limit exceeded: {e}")
print("💡 Wait a few minutes or upgrade your plan")
return False
except ClioUploadError as e:
print(f"📤 Upload failed: {e}")
print("💡 Check your network connection and retry")
return False
except ClioConfigError as e:
print(f"⚙️ Configuration error: {e}")
print("💡 Fix your configuration and try again")
return False
except ClioError as e:
print(f"❌ Unexpected Clio error: {e}")
print("💡 Contact support if this persists")
return False
except Exception as e:
print(f"💥 Unexpected error: {e}")
return False
print("✅ Automation completed successfully")
return True

Continue testing even if monitoring fails:

async def robust_automation():
monitor = None
try:
monitor = ClioMonitor(api_key=os.getenv("CLIO_API_KEY"))
except ClioError as e:
print(f"⚠️ Monitoring unavailable: {e}")
print("🔄 Continuing without monitoring...")
async with async_playwright() as p:
browser = await p.chromium.launch()
context = await browser.new_context(record_video_dir="./videos")
# Try to start monitoring, but continue if it fails
if monitor:
try:
await monitor.start_run(context, "Resilient Test")
print("📹 Monitoring enabled")
except ClioError as e:
print(f"⚠️ Monitoring failed: {e}")
print("🔄 Continuing without monitoring...")
# Run your test regardless
page = await context.new_page()
await page.goto("https://example.com")
await context.close()
print("✅ Test completed")

Implement custom retry logic for transient errors:

import asyncio
from clio.exceptions import ClioUploadError, ClioRateLimitError
async def automation_with_retries(max_retries=3):
for attempt in range(max_retries):
try:
monitor = ClioMonitor(api_key=os.getenv("CLIO_API_KEY"))
async with async_playwright() as p:
browser = await p.chromium.launch()
context = await browser.new_context(record_video_dir="./videos")
await monitor.start_run(context, f"Test Attempt {attempt + 1}")
# Your automation code
page = await context.new_page()
await page.goto("https://example.com")
await context.close()
print("✅ Success!")
return True
except (ClioUploadError, ClioRateLimitError) as e:
print(f"⚠️ Attempt {attempt + 1} failed: {e}")
if attempt < max_retries - 1:
wait_time = 2 ** attempt # Exponential backoff
print(f"⏳ Waiting {wait_time} seconds before retry...")
await asyncio.sleep(wait_time)
else:
print("❌ All retry attempts failed")
return False
except ClioError as e:
print(f"❌ Non-retryable error: {e}")
return False
return False

Use raise_on_error=True during development to get detailed error information:

# Development configuration - raises exceptions
monitor = ClioMonitor(
api_key="clio_abc123",
raise_on_error=True # Get full exception details
)
try:
await monitor.start_run(context, "Debug Test")
except ClioError as e:
# Full exception with stack trace
import traceback
traceback.print_exc()
print(f"Detailed error: {e}")

Use raise_on_error=False in production for graceful error handling:

# Production configuration - logs errors but continues
monitor = ClioMonitor(
api_key="clio_abc123",
raise_on_error=False # Log errors, don't raise exceptions
)
# Errors are logged but don't interrupt your automation
await monitor.start_run(context, "Production Test")
# Continues even if monitoring fails

Combine exception handling with proper logging:

import logging
from clio.exceptions import ClioError
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
async def logged_automation():
try:
monitor = ClioMonitor(api_key=os.getenv("CLIO_API_KEY"))
logger.info("🎬 Starting monitored automation")
# ... automation code ...
logger.info("✅ Automation completed successfully")
except ClioAuthError as e:
logger.error(f"Authentication failed: {e}")
# Send alert to monitoring system
except ClioUploadError as e:
logger.warning(f"Upload failed: {e}")
# Continue with test, upload failure not critical
except ClioError as e:
logger.error(f"Clio SDK error: {e}")
# Log for debugging but don't fail the test
  1. Catch specific exceptions rather than generic Exception:

    # Good
    except ClioAuthError as e:
    handle_auth_error(e)
    # Avoid
    except Exception as e:
    # Too broad, might catch unrelated errors
  2. Use appropriate error handling for your context:

    # Development - want to see all errors
    monitor = ClioMonitor(api_key="...", raise_on_error=True)
    # Production - graceful degradation
    monitor = ClioMonitor(api_key="...", raise_on_error=False)
  3. Implement retry logic for transient errors:

    # Retry upload errors and rate limits
    except (ClioUploadError, ClioRateLimitError):
    # Implement exponential backoff
  4. Log errors appropriately:

    # Include context but not sensitive data
    logger.error(f"Upload failed for run {run_name}: {e}")
  5. Provide user-friendly error messages:

    except ClioAuthError:
    print("❌ Authentication failed. Please check your API key.")