|
import requests |
|
import json |
|
import time |
|
import sys |
|
import base64 |
|
import os |
|
from typing import Dict, Any |
|
|
|
class Crawl4AiTester: |
|
def __init__(self, base_url: str = "http://localhost:11235", api_token: str = None): |
|
self.base_url = base_url |
|
self.api_token = api_token or os.getenv('CRAWL4AI_API_TOKEN') |
|
self.headers = {'Authorization': f'Bearer {self.api_token}'} if self.api_token else {} |
|
|
|
def submit_and_wait(self, request_data: Dict[str, Any], timeout: int = 300) -> Dict[str, Any]: |
|
|
|
response = requests.post(f"{self.base_url}/crawl", json=request_data, headers=self.headers) |
|
if response.status_code == 403: |
|
raise Exception("API token is invalid or missing") |
|
task_id = response.json()["task_id"] |
|
print(f"Task ID: {task_id}") |
|
|
|
|
|
start_time = time.time() |
|
while True: |
|
if time.time() - start_time > timeout: |
|
raise TimeoutError(f"Task {task_id} did not complete within {timeout} seconds") |
|
|
|
result = requests.get(f"{self.base_url}/task/{task_id}", headers=self.headers) |
|
status = result.json() |
|
|
|
if status["status"] == "failed": |
|
print("Task failed:", status.get("error")) |
|
raise Exception(f"Task failed: {status.get('error')}") |
|
|
|
if status["status"] == "completed": |
|
return status |
|
|
|
time.sleep(2) |
|
|
|
def submit_sync(self, request_data: Dict[str, Any]) -> Dict[str, Any]: |
|
response = requests.post(f"{self.base_url}/crawl_sync", json=request_data, headers=self.headers, timeout=60) |
|
if response.status_code == 408: |
|
raise TimeoutError("Task did not complete within server timeout") |
|
response.raise_for_status() |
|
return response.json() |
|
|
|
def test_docker_deployment(version="basic"): |
|
tester = Crawl4AiTester( |
|
|
|
base_url="https://crawl4ai-sby74.ondigitalocean.app", |
|
api_token="test" |
|
) |
|
print(f"Testing Crawl4AI Docker {version} version") |
|
|
|
|
|
max_retries = 5 |
|
for i in range(max_retries): |
|
try: |
|
health = requests.get(f"{tester.base_url}/health", timeout=10) |
|
print("Health check:", health.json()) |
|
break |
|
except requests.exceptions.RequestException as e: |
|
if i == max_retries - 1: |
|
print(f"Failed to connect after {max_retries} attempts") |
|
sys.exit(1) |
|
print(f"Waiting for service to start (attempt {i+1}/{max_retries})...") |
|
time.sleep(5) |
|
|
|
|
|
test_basic_crawl(tester) |
|
test_basic_crawl(tester) |
|
test_basic_crawl_sync(tester) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_basic_crawl(tester: Crawl4AiTester): |
|
print("\n=== Testing Basic Crawl ===") |
|
request = { |
|
"urls": "https://www.nbcnews.com/business", |
|
"priority": 10, |
|
"session_id": "test" |
|
} |
|
|
|
result = tester.submit_and_wait(request) |
|
print(f"Basic crawl result length: {len(result['result']['markdown'])}") |
|
assert result["result"]["success"] |
|
assert len(result["result"]["markdown"]) > 0 |
|
|
|
def test_basic_crawl_sync(tester: Crawl4AiTester): |
|
print("\n=== Testing Basic Crawl (Sync) ===") |
|
request = { |
|
"urls": "https://www.nbcnews.com/business", |
|
"priority": 10, |
|
"session_id": "test" |
|
} |
|
|
|
result = tester.submit_sync(request) |
|
print(f"Basic crawl result length: {len(result['result']['markdown'])}") |
|
assert result['status'] == 'completed' |
|
assert result['result']['success'] |
|
assert len(result['result']['markdown']) > 0 |
|
|
|
def test_js_execution(tester: Crawl4AiTester): |
|
print("\n=== Testing JS Execution ===") |
|
request = { |
|
"urls": "https://www.nbcnews.com/business", |
|
"priority": 8, |
|
"js_code": [ |
|
"const loadMoreButton = Array.from(document.querySelectorAll('button')).find(button => button.textContent.includes('Load More')); loadMoreButton && loadMoreButton.click();" |
|
], |
|
"wait_for": "article.tease-card:nth-child(10)", |
|
"crawler_params": { |
|
"headless": True |
|
} |
|
} |
|
|
|
result = tester.submit_and_wait(request) |
|
print(f"JS execution result length: {len(result['result']['markdown'])}") |
|
assert result["result"]["success"] |
|
|
|
def test_css_selector(tester: Crawl4AiTester): |
|
print("\n=== Testing CSS Selector ===") |
|
request = { |
|
"urls": "https://www.nbcnews.com/business", |
|
"priority": 7, |
|
"css_selector": ".wide-tease-item__description", |
|
"crawler_params": { |
|
"headless": True |
|
}, |
|
"extra": {"word_count_threshold": 10} |
|
|
|
} |
|
|
|
result = tester.submit_and_wait(request) |
|
print(f"CSS selector result length: {len(result['result']['markdown'])}") |
|
assert result["result"]["success"] |
|
|
|
def test_structured_extraction(tester: Crawl4AiTester): |
|
print("\n=== Testing Structured Extraction ===") |
|
schema = { |
|
"name": "Coinbase Crypto Prices", |
|
"baseSelector": ".cds-tableRow-t45thuk", |
|
"fields": [ |
|
{ |
|
"name": "crypto", |
|
"selector": "td:nth-child(1) h2", |
|
"type": "text", |
|
}, |
|
{ |
|
"name": "symbol", |
|
"selector": "td:nth-child(1) p", |
|
"type": "text", |
|
}, |
|
{ |
|
"name": "price", |
|
"selector": "td:nth-child(2)", |
|
"type": "text", |
|
} |
|
], |
|
} |
|
|
|
request = { |
|
"urls": "https://www.coinbase.com/explore", |
|
"priority": 9, |
|
"extraction_config": { |
|
"type": "json_css", |
|
"params": { |
|
"schema": schema |
|
} |
|
} |
|
} |
|
|
|
result = tester.submit_and_wait(request) |
|
extracted = json.loads(result["result"]["extracted_content"]) |
|
print(f"Extracted {len(extracted)} items") |
|
print("Sample item:", json.dumps(extracted[0], indent=2)) |
|
assert result["result"]["success"] |
|
assert len(extracted) > 0 |
|
|
|
def test_llm_extraction(tester: Crawl4AiTester): |
|
print("\n=== Testing LLM Extraction ===") |
|
schema = { |
|
"type": "object", |
|
"properties": { |
|
"model_name": { |
|
"type": "string", |
|
"description": "Name of the OpenAI model." |
|
}, |
|
"input_fee": { |
|
"type": "string", |
|
"description": "Fee for input token for the OpenAI model." |
|
}, |
|
"output_fee": { |
|
"type": "string", |
|
"description": "Fee for output token for the OpenAI model." |
|
} |
|
}, |
|
"required": ["model_name", "input_fee", "output_fee"] |
|
} |
|
|
|
request = { |
|
"urls": "https://openai.com/api/pricing", |
|
"priority": 8, |
|
"extraction_config": { |
|
"type": "llm", |
|
"params": { |
|
"provider": "openai/gpt-4o-mini", |
|
"api_token": os.getenv("OPENAI_API_KEY"), |
|
"schema": schema, |
|
"extraction_type": "schema", |
|
"instruction": """From the crawled content, extract all mentioned model names along with their fees for input and output tokens.""" |
|
} |
|
}, |
|
"crawler_params": {"word_count_threshold": 1} |
|
} |
|
|
|
try: |
|
result = tester.submit_and_wait(request) |
|
extracted = json.loads(result["result"]["extracted_content"]) |
|
print(f"Extracted {len(extracted)} model pricing entries") |
|
print("Sample entry:", json.dumps(extracted[0], indent=2)) |
|
assert result["result"]["success"] |
|
except Exception as e: |
|
print(f"LLM extraction test failed (might be due to missing API key): {str(e)}") |
|
|
|
def test_llm_with_ollama(tester: Crawl4AiTester): |
|
print("\n=== Testing LLM with Ollama ===") |
|
schema = { |
|
"type": "object", |
|
"properties": { |
|
"article_title": { |
|
"type": "string", |
|
"description": "The main title of the news article" |
|
}, |
|
"summary": { |
|
"type": "string", |
|
"description": "A brief summary of the article content" |
|
}, |
|
"main_topics": { |
|
"type": "array", |
|
"items": {"type": "string"}, |
|
"description": "Main topics or themes discussed in the article" |
|
} |
|
} |
|
} |
|
|
|
request = { |
|
"urls": "https://www.nbcnews.com/business", |
|
"priority": 8, |
|
"extraction_config": { |
|
"type": "llm", |
|
"params": { |
|
"provider": "ollama/llama2", |
|
"schema": schema, |
|
"extraction_type": "schema", |
|
"instruction": "Extract the main article information including title, summary, and main topics." |
|
} |
|
}, |
|
"extra": {"word_count_threshold": 1}, |
|
"crawler_params": {"verbose": True} |
|
} |
|
|
|
try: |
|
result = tester.submit_and_wait(request) |
|
extracted = json.loads(result["result"]["extracted_content"]) |
|
print("Extracted content:", json.dumps(extracted, indent=2)) |
|
assert result["result"]["success"] |
|
except Exception as e: |
|
print(f"Ollama extraction test failed: {str(e)}") |
|
|
|
def test_cosine_extraction(tester: Crawl4AiTester): |
|
print("\n=== Testing Cosine Extraction ===") |
|
request = { |
|
"urls": "https://www.nbcnews.com/business", |
|
"priority": 8, |
|
"extraction_config": { |
|
"type": "cosine", |
|
"params": { |
|
"semantic_filter": "business finance economy", |
|
"word_count_threshold": 10, |
|
"max_dist": 0.2, |
|
"top_k": 3 |
|
} |
|
} |
|
} |
|
|
|
try: |
|
result = tester.submit_and_wait(request) |
|
extracted = json.loads(result["result"]["extracted_content"]) |
|
print(f"Extracted {len(extracted)} text clusters") |
|
print("First cluster tags:", extracted[0]["tags"]) |
|
assert result["result"]["success"] |
|
except Exception as e: |
|
print(f"Cosine extraction test failed: {str(e)}") |
|
|
|
def test_screenshot(tester: Crawl4AiTester): |
|
print("\n=== Testing Screenshot ===") |
|
request = { |
|
"urls": "https://www.nbcnews.com/business", |
|
"priority": 5, |
|
"screenshot": True, |
|
"crawler_params": { |
|
"headless": True |
|
} |
|
} |
|
|
|
result = tester.submit_and_wait(request) |
|
print("Screenshot captured:", bool(result["result"]["screenshot"])) |
|
|
|
if result["result"]["screenshot"]: |
|
|
|
screenshot_data = base64.b64decode(result["result"]["screenshot"]) |
|
with open("test_screenshot.jpg", "wb") as f: |
|
f.write(screenshot_data) |
|
print("Screenshot saved as test_screenshot.jpg") |
|
|
|
assert result["result"]["success"] |
|
|
|
if __name__ == "__main__": |
|
version = sys.argv[1] if len(sys.argv) > 1 else "basic" |
|
|
|
test_docker_deployment(version) |