Skip to content

Commit 04a3c70

Browse files
committed
feat: Add test suite for CloudflareBypasser, including cookie generation, HTML retrieval, and server endpoints
1 parent f4e0234 commit 04a3c70

10 files changed

Lines changed: 929 additions & 0 deletions

File tree

.github/workflows/tests.yml

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
name: CloudflareBypassForScraping Tests
2+
3+
on:
4+
push:
5+
branches: [ main, develop ]
6+
pull_request:
7+
branches: [ main, develop ]
8+
workflow_dispatch: # Allow manual triggers
9+
schedule:
10+
# Run tests daily at 2 AM UTC
11+
- cron: '0 2 * * *'
12+
13+
jobs:
14+
test:
15+
runs-on: ubuntu-latest
16+
timeout-minutes: 60
17+
18+
strategy:
19+
matrix:
20+
python-version: ['3.10', '3.11']
21+
fail-fast: false
22+
23+
steps:
24+
- name: Checkout code
25+
uses: actions/checkout@v4
26+
27+
- name: Set up Python ${{ matrix.python-version }}
28+
uses: actions/setup-python@v4
29+
with:
30+
python-version: ${{ matrix.python-version }}
31+
cache: 'pip'
32+
33+
- name: Install system dependencies
34+
run: |
35+
sudo apt-get update
36+
sudo apt-get install -y \
37+
libnss3 \
38+
libnspr4 \
39+
libatk1.0-0 \
40+
libatk-bridge2.0-0 \
41+
libcups2 \
42+
libdrm2 \
43+
libxkbcommon0 \
44+
libxcomposite1 \
45+
libxdamage1 \
46+
libxfixes3 \
47+
libxrandr2 \
48+
libgbm1 \
49+
libasound2 \
50+
libpango-1.0-0 \
51+
libcairo2
52+
53+
- name: Install Python dependencies
54+
run: |
55+
python -m pip install --upgrade pip setuptools wheel
56+
pip install -r requirements.txt
57+
pip install -r server_requirements.txt
58+
pip install -r tests/test-requirements.txt
59+
60+
- name: Show installed packages
61+
run: |
62+
pip list
63+
64+
- name: Run Cookie Generation Tests
65+
run: |
66+
python -m pytest -c tests/pytest.ini tests/test_cookies.py -v --tb=short --timeout=120
67+
timeout-minutes: 30
68+
69+
- name: Run HTML Retrieval Tests
70+
run: |
71+
python -m pytest -c tests/pytest.ini tests/test_html.py -v --tb=short --timeout=120
72+
timeout-minutes: 30
73+
74+
- name: Start Server for Integration Tests
75+
run: |
76+
python server.py &
77+
SERVER_PID=$!
78+
echo "SERVER_PID=$SERVER_PID" >> $GITHUB_ENV
79+
sleep 10
80+
curl -f http://localhost:8000/cache/stats || exit 1
81+
82+
- name: Run Server Endpoint Tests
83+
run: |
84+
python -m pytest -c tests/pytest.ini tests/test_server.py -v --tb=short --timeout=120
85+
timeout-minutes: 20
86+
87+
- name: Run Mirror Request Tests
88+
run: |
89+
python -m pytest -c tests/pytest.ini tests/test_mirror.py -v --tb=short --timeout=120
90+
timeout-minutes: 30
91+
92+
- name: Stop Server
93+
if: always()
94+
run: |
95+
if [ ! -z "$SERVER_PID" ]; then
96+
kill $SERVER_PID || true
97+
fi
98+
pkill -f "python.*server.py" || true
99+
100+
- name: Generate Test Report
101+
if: always()
102+
run: |
103+
echo "## Test Summary" >> $GITHUB_STEP_SUMMARY
104+
echo "" >> $GITHUB_STEP_SUMMARY
105+
echo "Tests completed for Python ${{ matrix.python-version }}" >> $GITHUB_STEP_SUMMARY
106+
107+
- name: Show logs on failure
108+
if: failure()
109+
run: |
110+
echo "=== Recent logs ==="
111+
tail -n 100 ~/.local/share/camoufox/logs/* || true
112+
echo "=== System info ==="
113+
uname -a
114+
python --version
115+
pip list
116+
117+
test-quick:
118+
name: Quick Tests (Single Request)
119+
runs-on: ubuntu-latest
120+
timeout-minutes: 20
121+
122+
steps:
123+
- name: Checkout code
124+
uses: actions/checkout@v4
125+
126+
- name: Set up Python
127+
uses: actions/setup-python@v4
128+
with:
129+
python-version: '3.10'
130+
cache: 'pip'
131+
132+
- name: Install dependencies
133+
run: |
134+
sudo apt-get update
135+
sudo apt-get install -y libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 libcups2 libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 libxfixes3 libxrandr2 libgbm1 libasound2
136+
python -m pip install --upgrade pip
137+
pip install -r requirements.txt
138+
pip install -r server_requirements.txt
139+
pip install -r tests/test-requirements.txt
140+
141+
- name: Run Quick Tests
142+
run: |
143+
python -m pytest -c tests/pytest.ini tests/test_cookies.py::test_single_cookie_generation -v
144+
python -m pytest -c tests/pytest.ini tests/test_html.py::test_single_html_retrieval -v
145+
timeout-minutes: 15
146+
147+
test-parallel:
148+
name: Parallel Tests (Concurrency)
149+
runs-on: ubuntu-latest
150+
timeout-minutes: 40
151+
152+
steps:
153+
- name: Checkout code
154+
uses: actions/checkout@v4
155+
156+
- name: Set up Python
157+
uses: actions/setup-python@v4
158+
with:
159+
python-version: '3.10'
160+
cache: 'pip'
161+
162+
- name: Install dependencies
163+
run: |
164+
sudo apt-get update
165+
sudo apt-get install -y libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 libcups2 libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 libxfixes3 libxrandr2 libgbm1 libasound2
166+
python -m pip install --upgrade pip
167+
pip install -r requirements.txt
168+
pip install -r server_requirements.txt
169+
pip install -r tests/test-requirements.txt
170+
171+
- name: Test Parallel Cookie Generation
172+
run: |
173+
python -m pytest -c tests/pytest.ini tests/test_cookies.py -k "parallel" -v --timeout=120
174+
timeout-minutes: 35

tests/__init__.py

Whitespace-only changes.

tests/conftest.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import pytest
2+
import asyncio
3+
from typing import AsyncGenerator
4+
5+
# Test configuration
6+
TEST_URL = "https://challenge.sarper.me"
7+
EXPECTED_SUCCESS_TEXT = "cloudflare challenged passed. You are now a free bot."
8+
TEST_TIMEOUT = 60 # seconds per test
9+
10+
11+
@pytest.fixture(scope="session")
12+
def event_loop():
13+
"""Create an instance of the default event loop for the test session."""
14+
loop = asyncio.get_event_loop_policy().new_event_loop()
15+
yield loop
16+
loop.close()
17+
18+
19+
@pytest.fixture
20+
def test_url():
21+
"""Provide the test URL with Cloudflare challenge."""
22+
return TEST_URL
23+
24+
25+
@pytest.fixture
26+
def expected_text():
27+
"""Provide the expected success text after bypass."""
28+
return EXPECTED_SUCCESS_TEXT
29+
30+
31+
@pytest.fixture
32+
async def bypasser():
33+
"""Create a CamoufoxBypasser instance for testing."""
34+
from cf_bypasser.core.bypasser import CamoufoxBypasser
35+
instance = CamoufoxBypasser(max_retries=5, log=True)
36+
yield instance
37+
# Cleanup is handled per-request now, but just in case
38+
await instance.cleanup()

tests/pytest.ini

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
[pytest]
2+
# Pytest configuration for CloudflareBypassForScraping
3+
4+
# Test discovery patterns
5+
python_files = test_*.py
6+
python_classes = Test*
7+
python_functions = test_*
8+
9+
# Async support
10+
asyncio_mode = auto
11+
asyncio_default_fixture_loop_scope = function
12+
13+
# Show extra test summary info
14+
addopts =
15+
-v
16+
--tb=short
17+
--strict-markers
18+
--disable-warnings
19+
-p no:warnings
20+
--color=yes
21+
--durations=10
22+
-p no:seleniumbase
23+
24+
# Test markers
25+
markers =
26+
asyncio: marks tests as async (deselect with '-m "not asyncio"')
27+
slow: marks tests as slow (deselect with '-m "not slow"')
28+
parallel: marks tests that run in parallel
29+
30+
# Logging
31+
log_cli = true
32+
log_cli_level = INFO
33+
log_cli_format = %(asctime)s [%(levelname)8s] %(message)s
34+
log_cli_date_format = %H:%M:%S
35+
36+
# Coverage options
37+
[coverage:run]
38+
source = cf_bypasser
39+
omit =
40+
*/tests/*
41+
*/test_*.py
42+
*/__pycache__/*

tests/run_tests.py

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Runs all tests
4+
"""
5+
import subprocess
6+
import sys
7+
import time
8+
import signal
9+
import os
10+
from pathlib import Path
11+
12+
13+
def print_banner(text):
14+
"""Print a formatted banner."""
15+
print("\n" + "=" * 80)
16+
print(f" {text}")
17+
print("=" * 80 + "\n")
18+
19+
20+
def start_server():
21+
"""Start the server in the background."""
22+
print("Starting server...")
23+
24+
# Start server process
25+
process = subprocess.Popen(
26+
[sys.executable, "server.py"],
27+
stdout=subprocess.PIPE,
28+
stderr=subprocess.PIPE,
29+
cwd=Path(__file__).parent.parent
30+
)
31+
32+
# Wait for server to start
33+
print("⏳ Waiting for server to initialize...")
34+
time.sleep(5)
35+
36+
# Check if server started successfully
37+
if process.poll() is not None:
38+
print("❌ Server failed to start!")
39+
return None
40+
41+
print("✅ Server started successfully\n")
42+
return process
43+
44+
45+
def stop_server(process):
46+
"""Stop the server process."""
47+
if process:
48+
print("\n🛑 Stopping server...")
49+
process.send_signal(signal.SIGTERM)
50+
try:
51+
process.wait(timeout=10)
52+
print("✅ Server stopped\n")
53+
except subprocess.TimeoutExpired:
54+
print("⚠️ Server didn't stop gracefully, forcing...")
55+
process.kill()
56+
process.wait()
57+
58+
59+
def run_tests(test_args=None):
60+
"""Run pytest with specified arguments."""
61+
cmd = [sys.executable, "-m", "pytest"]
62+
63+
if test_args:
64+
cmd.extend(test_args)
65+
else:
66+
# Default: run all tests with verbose output
67+
cmd.extend([
68+
"-c", "tests/pytest.ini",
69+
"tests/",
70+
"-v",
71+
"--tb=short",
72+
"--color=yes"
73+
])
74+
75+
print_banner("Running Tests")
76+
print(f"Command: {' '.join(cmd)}\n")
77+
78+
result = subprocess.run(cmd, cwd=Path(__file__).parent.parent)
79+
return result.returncode
80+
81+
82+
def main():
83+
"""Main test runner."""
84+
print_banner("CloudflareBypassForScraping Test Suite")
85+
86+
# Check if pytest is installed
87+
try:
88+
subprocess.run(
89+
[sys.executable, "-m", "pytest", "--version"],
90+
check=True,
91+
capture_output=True
92+
)
93+
except subprocess.CalledProcessError:
94+
print("pytest is not installed!")
95+
print("Install test requirements with: pip install -r tests/test-requirements.txt")
96+
return 1
97+
98+
server_process = None
99+
100+
try:
101+
# Start server
102+
server_process = start_server()
103+
if not server_process:
104+
return 1
105+
106+
# Run tests
107+
test_args = sys.argv[1:] if len(sys.argv) > 1 else None
108+
exit_code = run_tests(test_args)
109+
110+
# Print summary
111+
print_banner("Test Results")
112+
if exit_code == 0:
113+
print("All tests passed!\n")
114+
else:
115+
print(f"Tests failed with exit code {exit_code}\n")
116+
117+
return exit_code
118+
119+
except KeyboardInterrupt:
120+
print("\n\n Tests interrupted by user")
121+
return 130
122+
123+
except Exception as e:
124+
print(f"\n Error running tests: {e}")
125+
return 1
126+
127+
finally:
128+
# Always stop the server
129+
stop_server(server_process)
130+
131+
132+
if __name__ == "__main__":
133+
sys.exit(main())

0 commit comments

Comments
 (0)