Download tests/contract/test_browser_api_contract.py from techprotrade/annator-command-center: direct link, hf CLI and curl.
- Browser
- Download file 19.9 kB
-
https://huggingface.co/spaces/techprotrade/annator-command-center/resolve/main/tests/contract/test_browser_api_contract.py
- Command line
-
hf download hf://spaces/techprotrade/annator-command-center/tests/contract/test_browser_api_contract.py
-
curl -L -o test_browser_api_contract.py https://huggingface.co/spaces/techprotrade/annator-command-center/resolve/main/tests/contract/test_browser_api_contract.py
19.9 kB
| """Browser API contract tests using Schemathesis for OpenAPI compliance. | |
| Validates that browser endpoints (session, navigation, interaction) conform to | |
| their OpenAPI specification. Browser endpoints handle Playwright-based browser | |
| automation for web scraping, form filling, and screenshots. | |
| Contract test coverage: | |
| - POST /api/browser/session - Create browser session | |
| - GET /api/browser/sessions - List browser sessions | |
| - DELETE /api/browser/session/{id} - Close browser session | |
| - POST /api/browser/navigate - Navigate to URL | |
| - POST /api/browser/click - Click element | |
| - POST /api/browser/fill - Fill form field | |
| - POST /api/browser/screenshot - Take screenshot | |
| """ | |
| import pytest | |
| from fastapi.testclient import TestClient | |
| from main_api_app import app | |
| from tests.contract.conftest import schema | |
| from unittest.mock import MagicMock, patch | |
| class TestBrowserSessionContract: | |
| """Contract tests for browser session management endpoints.""" | |
| def test_create_session_contracts(self): | |
| """Test POST /api/browser/session validates response schema.""" | |
| if "/api/browser/session" in schema: | |
| operation = schema["/api/browser/session"]["POST"] | |
| with TestClient(app) as client: | |
| response = client.post("/api/browser/session") | |
| # Validate response against OpenAPI schema | |
| operation.validate_response(response) | |
| # May return 200, 400, 401, 403, or 500 if Playwright not available | |
| assert response.status_code in [200, 400, 401, 403, 500, 503] | |
| else: | |
| pytest.skip("Endpoint not in OpenAPI schema") | |
| def test_create_session_with_config(self): | |
| """Test that session creation config conforms to schema.""" | |
| if "/api/browser/session" in schema: | |
| with TestClient(app) as client: | |
| response = client.post( | |
| "/api/browser/session", | |
| json={ | |
| "headless": True, | |
| "browser_type": "chromium" | |
| } | |
| ) | |
| assert response.status_code in [200, 400, 401, 403, 500, 503] | |
| else: | |
| pytest.skip("Endpoint not in OpenAPI schema") | |
| def test_list_sessions_contracts(self): | |
| """Test GET /api/browser/sessions validates response schema.""" | |
| if "/api/browser/sessions" in schema: | |
| operation = schema["/api/browser/sessions"]["GET"] | |
| with TestClient(app) as client: | |
| response = client.get("/api/browser/sessions") | |
| # Validate response against OpenAPI schema | |
| operation.validate_response(response) | |
| assert response.status_code in [200, 401, 403] | |
| else: | |
| pytest.skip("Endpoint not in OpenAPI schema") | |
| def test_close_session_contracts(self): | |
| """Test DELETE /api/browser/session/{id} validates response schema.""" | |
| if "/api/browser/session/{session_id}" in schema: | |
| operation = schema["/api/browser/session/{session_id}"]["DELETE"] | |
| with TestClient(app) as client: | |
| response = client.delete("/api/browser/session/test-session-id") | |
| # Validate response against OpenAPI schema | |
| operation.validate_response(response) | |
| assert response.status_code in [200, 204, 401, 403, 404] | |
| else: | |
| pytest.skip("Endpoint not in OpenAPI schema") | |
| def test_session_not_found(self): | |
| """Test that deleting non-existent session returns 404.""" | |
| if "/api/browser/session/{session_id}" in schema: | |
| with TestClient(app) as client: | |
| response = client.delete("/api/browser/session/nonexistent-session-999") | |
| assert response.status_code in [200, 204, 401, 403, 404] | |
| else: | |
| pytest.skip("Endpoint not in OpenAPI schema") | |
| class TestBrowserNavigationContract: | |
| """Contract tests for browser navigation endpoints.""" | |
| def test_navigate_contracts(self): | |
| """Test POST /api/browser/navigate validates response schema.""" | |
| if "/api/browser/navigate" in schema: | |
| operation = schema["/api/browser/navigate"]["POST"] | |
| with TestClient(app) as client: | |
| response = client.post( | |
| "/api/browser/navigate", | |
| json={ | |
| "session_id": "test-session", | |
| "url": "https://example.com" | |
| } | |
| ) | |
| # Validate response against OpenAPI schema | |
| operation.validate_response(response) | |
| assert response.status_code in [200, 400, 401, 403, 422, 500] | |
| else: | |
| pytest.skip("Endpoint not in OpenAPI schema") | |
| def test_navigate_request_schema(self): | |
| """Test that URL parameter validation conforms to schema.""" | |
| if "/api/browser/navigate" in schema: | |
| with TestClient(app) as client: | |
| # Test with valid URL | |
| response = client.post( | |
| "/api/browser/navigate", | |
| json={ | |
| "session_id": "test-session", | |
| "url": "https://example.com/page" | |
| } | |
| ) | |
| assert response.status_code in [200, 400, 401, 403, 422, 500] | |
| else: | |
| pytest.skip("Endpoint not in OpenAPI schema") | |
| def test_navigate_errors(self): | |
| """Test that 400/422 schemas work for invalid URLs.""" | |
| if "/api/browser/navigate" in schema: | |
| with TestClient(app) as client: | |
| # Test with invalid URL (missing required field) | |
| response = client.post( | |
| "/api/browser/navigate", | |
| json={ | |
| "session_id": "test-session" | |
| # Missing url field | |
| } | |
| ) | |
| # Should return 400 or 422 | |
| assert response.status_code in [200, 400, 401, 403, 422, 500] | |
| # Test with malformed URL | |
| response = client.post( | |
| "/api/browser/navigate", | |
| json={ | |
| "session_id": "test-session", | |
| "url": "not-a-valid-url" | |
| } | |
| ) | |
| assert response.status_code in [200, 400, 401, 403, 422, 500] | |
| else: | |
| pytest.skip("Endpoint not in OpenAPI schema") | |
| def test_navigate_with_options(self): | |
| """Test that navigation options conform to schema.""" | |
| if "/api/browser/navigate" in schema: | |
| with TestClient(app) as client: | |
| response = client.post( | |
| "/api/browser/navigate", | |
| json={ | |
| "session_id": "test-session", | |
| "url": "https://example.com", | |
| "wait_until": "networkidle", | |
| "timeout": 30000 | |
| } | |
| ) | |
| assert response.status_code in [200, 400, 401, 403, 422, 500] | |
| else: | |
| pytest.skip("Endpoint not in OpenAPI schema") | |
| class TestBrowserInteractionContract: | |
| """Contract tests for browser interaction endpoints.""" | |
| def test_click_contracts(self): | |
| """Test POST /api/browser/click validates response schema.""" | |
| if "/api/browser/click" in schema: | |
| operation = schema["/api/browser/click"]["POST"] | |
| with TestClient(app) as client: | |
| response = client.post( | |
| "/api/browser/click", | |
| json={ | |
| "session_id": "test-session", | |
| "selector": "#submit-button" | |
| } | |
| ) | |
| # Validate response against OpenAPI schema | |
| operation.validate_response(response) | |
| assert response.status_code in [200, 400, 401, 403, 422, 500] | |
| else: | |
| pytest.skip("Endpoint not in OpenAPI schema") | |
| def test_fill_contracts(self): | |
| """Test POST /api/browser/fill validates response schema.""" | |
| if "/api/browser/fill" in schema: | |
| operation = schema["/api/browser/fill"]["POST"] | |
| with TestClient(app) as client: | |
| response = client.post( | |
| "/api/browser/fill", | |
| json={ | |
| "session_id": "test-session", | |
| "selector": "#username", | |
| "value": "testuser" | |
| } | |
| ) | |
| # Validate response against OpenAPI schema | |
| operation.validate_response(response) | |
| assert response.status_code in [200, 400, 401, 403, 422, 500] | |
| else: | |
| pytest.skip("Endpoint not in OpenAPI schema") | |
| def test_fill_multiple_fields(self): | |
| """Test that filling multiple fields conforms to schema.""" | |
| if "/api/browser/fill" in schema: | |
| with TestClient(app) as client: | |
| # Test with fill array (if supported) | |
| response = client.post( | |
| "/api/browser/fill", | |
| json={ | |
| "session_id": "test-session", | |
| "fields": [ | |
| {"selector": "#username", "value": "user"}, | |
| {"selector": "#password", "value": "pass"} | |
| ] | |
| } | |
| ) | |
| assert response.status_code in [200, 400, 401, 403, 422, 500] | |
| else: | |
| pytest.skip("Endpoint not in OpenAPI schema") | |
| def test_screenshot_contracts(self): | |
| """Test POST /api/browser/screenshot validates response schema.""" | |
| if "/api/browser/screenshot" in schema: | |
| operation = schema["/api/browser/screenshot"]["POST"] | |
| with TestClient(app) as client: | |
| response = client.post( | |
| "/api/browser/screenshot", | |
| json={ | |
| "session_id": "test-session" | |
| } | |
| ) | |
| # Validate response against OpenAPI schema | |
| operation.validate_response(response) | |
| assert response.status_code in [200, 400, 401, 403, 500] | |
| else: | |
| pytest.skip("Endpoint not in OpenAPI schema") | |
| def test_screenshot_response(self): | |
| """Test that screenshot response includes Base64 image.""" | |
| if "/api/browser/screenshot" in schema: | |
| with TestClient(app) as client: | |
| response = client.post( | |
| "/api/browser/screenshot", | |
| json={ | |
| "session_id": "test-session", | |
| "format": "png" | |
| } | |
| ) | |
| # If successful, should have image data | |
| if response.status_code == 200: | |
| json_resp = response.json() | |
| # Should have base64 image data | |
| assert "image" in json_resp or "data" in json_resp | |
| else: | |
| pytest.skip("Endpoint not in OpenAPI schema") | |
| def test_screenshot_options(self): | |
| """Test that screenshot options conform to schema.""" | |
| if "/api/browser/screenshot" in schema: | |
| with TestClient(app) as client: | |
| response = client.post( | |
| "/api/browser/screenshot", | |
| json={ | |
| "session_id": "test-session", | |
| "format": "jpeg", | |
| "quality": 80, | |
| "full_page": True | |
| } | |
| ) | |
| assert response.status_code in [200, 400, 401, 403, 500] | |
| else: | |
| pytest.skip("Endpoint not in OpenAPI schema") | |
| def test_execute_script_contracts(self): | |
| """Test POST /api/browser/execute validates response schema.""" | |
| if "/api/browser/execute" in schema: | |
| operation = schema["/api/browser/execute"]["POST"] | |
| with TestClient(app) as client: | |
| response = client.post( | |
| "/api/browser/execute", | |
| json={ | |
| "session_id": "test-session", | |
| "script": "document.title" | |
| } | |
| ) | |
| # Validate response against OpenAPI schema | |
| operation.validate_response(response) | |
| assert response.status_code in [200, 400, 401, 403, 422, 500] | |
| else: | |
| pytest.skip("Endpoint not in OpenAPI schema") | |
| class TestBrowserGovernanceContract: | |
| """Contract tests for browser governance and permissions.""" | |
| def test_governance_headers(self): | |
| """Test that X-Agent-Maturity header is processed.""" | |
| with TestClient(app) as client: | |
| # Test with governance header | |
| if "/api/browser/session" in schema: | |
| response = client.post( | |
| "/api/browser/session", | |
| headers={"X-Agent-Maturity": "SUPERVISED"} | |
| ) | |
| # Header may or may not be enforced in contract tests | |
| assert response.status_code in [200, 400, 401, 403, 500, 503] | |
| def test_permission_denied(self): | |
| """Test that 403 response conforms to schema.""" | |
| # Browser operations require INTERN+ maturity | |
| # This test validates the 403 response schema | |
| if "/api/browser/session" in schema: | |
| with TestClient(app) as client: | |
| response = client.post("/api/browser/session") | |
| # If permission denied, should return 403 | |
| if response.status_code == 403: | |
| # Validate error response structure | |
| json_resp = response.json() | |
| assert "detail" in json_resp or "error" in json_resp | |
| else: | |
| pytest.skip("Endpoint not in OpenAPI schema") | |
| def test_unauthorized(self): | |
| """Test that 401 response conforms to schema.""" | |
| if "/api/browser/session" in schema: | |
| with TestClient(app) as client: | |
| response = client.post("/api/browser/session") | |
| # If unauthorized, should return 401 | |
| if response.status_code == 401: | |
| # Validate error response structure | |
| json_resp = response.json() | |
| assert "detail" in json_resp or "error" in json_resp | |
| else: | |
| pytest.skip("Endpoint not in OpenAPI schema") | |
| class TestBrowserErrorHandlingContract: | |
| """Contract tests for browser error handling.""" | |
| def test_session_timeout(self): | |
| """Test that session timeout returns appropriate error.""" | |
| if "/api/browser/navigate" in schema: | |
| with TestClient(app) as client: | |
| # Test with expired session | |
| response = client.post( | |
| "/api/browser/navigate", | |
| json={ | |
| "session_id": "expired-session", | |
| "url": "https://example.com" | |
| } | |
| ) | |
| # Should return error for expired session | |
| assert response.status_code in [200, 400, 401, 403, 404, 500] | |
| else: | |
| pytest.skip("Endpoint not in OpenAPI schema") | |
| def test_element_not_found(self): | |
| """Test that missing element returns appropriate error.""" | |
| if "/api/browser/click" in schema: | |
| with TestClient(app) as client: | |
| response = client.post( | |
| "/api/browser/click", | |
| json={ | |
| "session_id": "test-session", | |
| "selector": "#nonexistent-element" | |
| } | |
| ) | |
| # Should return error if element not found | |
| assert response.status_code in [200, 400, 401, 403, 404, 500] | |
| else: | |
| pytest.skip("Endpoint not in OpenAPI schema") | |
| def test_navigation_timeout(self): | |
| """Test that navigation timeout returns appropriate error.""" | |
| if "/api/browser/navigate" in schema: | |
| with TestClient(app) as client: | |
| response = client.post( | |
| "/api/browser/navigate", | |
| json={ | |
| "session_id": "test-session", | |
| "url": "https://example.com", | |
| "timeout": 1 # 1ms timeout (will fail) | |
| } | |
| ) | |
| # Should return error for timeout | |
| assert response.status_code in [200, 400, 401, 403, 500] | |
| else: | |
| pytest.skip("Endpoint not in OpenAPI schema") | |
| class TestBrowserCDPContract: | |
| """Contract tests for Chrome DevTools Protocol (CDP) endpoints.""" | |
| def test_cdp_session_contracts(self): | |
| """Test CDP session creation validates schema.""" | |
| if "/api/browser/cdp" in schema: | |
| operation = schema["/api/browser/cdp"]["POST"] | |
| with TestClient(app) as client: | |
| response = client.post( | |
| "/api/browser/cdp", | |
| json={ | |
| "session_id": "test-session" | |
| } | |
| ) | |
| # Validate response against OpenAPI schema | |
| operation.validate_response(response) | |
| assert response.status_code in [200, 400, 401, 403, 500] | |
| else: | |
| pytest.skip("Endpoint not in OpenAPI schema") | |
| def test_cdp_execute_contracts(self): | |
| """Test CDP command execution validates schema.""" | |
| # CDP allows sending arbitrary Chrome DevTools Protocol commands | |
| # Schema validation is more complex due to dynamic command structure | |
| if "/api/browser/cdp/execute" in schema: | |
| operation = schema["/api/browser/cdp/execute"]["POST"] | |
| with TestClient(app) as client: | |
| response = client.post( | |
| "/api/browser/cdp/execute", | |
| json={ | |
| "session_id": "test-session", | |
| "command": "Page.navigate", | |
| "params": {"url": "https://example.com"} | |
| } | |
| ) | |
| # Validate response against OpenAPI schema | |
| operation.validate_response(response) | |
| assert response.status_code in [200, 400, 401, 403, 422, 500] | |
| else: | |
| pytest.skip("Endpoint not in OpenAPI schema") | |
| class TestBrowserInputStrategies: | |
| """Hypothesis strategies for browser-specific input generation.""" | |
| def test_valid_url_strategy(self): | |
| """Test that URL strategy generates valid URLs.""" | |
| # This documents the URL validation strategy for Hypothesis | |
| # Valid URLs should be absolute and well-formed | |
| valid_urls = [ | |
| "https://example.com", | |
| "https://example.com/page", | |
| "https://example.com/page?query=value", | |
| "https://example.com:8080/page" | |
| ] | |
| # These should all pass URL validation | |
| for url in valid_urls: | |
| # In actual implementation, would use hypothesis.strategies | |
| assert url.startswith(("http://", "https://")) | |
| def test_selector_strategy(self): | |
| """Test that selector strategy generates valid CSS selectors.""" | |
| # This documents the CSS selector validation strategy | |
| valid_selectors = [ | |
| "#id", | |
| ".class", | |
| "tag", | |
| "#id .class", | |
| "tag[attr=value]", | |
| "parent > child" | |
| ] | |
| # These should all be valid CSS selectors | |
| for selector in valid_selectors: | |
| # In actual implementation, would use hypothesis.strategies | |
| assert len(selector) > 0 | |