Skip to content

Commit 79ca20f

Browse files
committed
Add integration tests
1 parent 2102c24 commit 79ca20f

2 files changed

Lines changed: 191 additions & 0 deletions

File tree

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
name: Integration Tests
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches: [main]
7+
8+
concurrency:
9+
group: python-integration-tests-${{ github.workflow }}-${{ github.ref }}
10+
cancel-in-progress: true
11+
12+
permissions:
13+
contents: read
14+
15+
jobs:
16+
integration-tests:
17+
runs-on: ubuntu-latest
18+
19+
steps:
20+
- name: Check out repository
21+
uses: actions/checkout@v6
22+
23+
- name: Set up Python
24+
uses: actions/setup-python@v6
25+
with:
26+
python-version: "3.12"
27+
28+
- name: Install Poetry
29+
run: python -m pip install --upgrade pip poetry
30+
31+
- name: Show tool versions
32+
run: |
33+
python --version
34+
poetry --version
35+
36+
- name: Install dependencies
37+
run: poetry install --with dev --no-interaction
38+
39+
- name: Run integration tests
40+
run: poetry run pytest tests/integration
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import json
2+
import threading
3+
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
4+
5+
import pytest
6+
7+
from hyperbrowser import AsyncHyperbrowser, Hyperbrowser
8+
from hyperbrowser.models.scrape import ScrapeOptions, StartScrapeJobParams
9+
10+
11+
def _read_json_body(handler: BaseHTTPRequestHandler):
12+
content_length = int(handler.headers.get("Content-Length", "0"))
13+
if content_length <= 0:
14+
return None
15+
return json.loads(handler.rfile.read(content_length).decode("utf-8"))
16+
17+
18+
def _send_json(handler: BaseHTTPRequestHandler, status_code: int, payload: dict) -> None:
19+
encoded = json.dumps(payload).encode("utf-8")
20+
handler.send_response(status_code)
21+
handler.send_header("Content-Type", "application/json")
22+
handler.send_header("Content-Length", str(len(encoded)))
23+
handler.end_headers()
24+
handler.wfile.write(encoded)
25+
26+
27+
def _start_server():
28+
requests = []
29+
30+
class Handler(BaseHTTPRequestHandler):
31+
def do_POST(self):
32+
requests.append(
33+
{
34+
"method": self.command,
35+
"path": self.path,
36+
"api_key": self.headers.get("x-api-key"),
37+
"content_type": self.headers.get("content-type"),
38+
"body": _read_json_body(self),
39+
}
40+
)
41+
42+
if self.path == "/api/scrape":
43+
_send_json(self, 200, {"jobId": "job_123"})
44+
return
45+
46+
_send_json(self, 404, {"message": f"unexpected route {self.path}"})
47+
48+
def do_GET(self):
49+
requests.append(
50+
{
51+
"method": self.command,
52+
"path": self.path,
53+
"api_key": self.headers.get("x-api-key"),
54+
"content_type": self.headers.get("content-type"),
55+
"body": None,
56+
}
57+
)
58+
59+
if self.path == "/api/scrape/job_123/status":
60+
_send_json(self, 200, {"status": "completed"})
61+
return
62+
63+
_send_json(self, 404, {"message": f"unexpected route {self.path}"})
64+
65+
def log_message(self, format, *args):
66+
return
67+
68+
server = ThreadingHTTPServer(("127.0.0.1", 0), Handler)
69+
thread = threading.Thread(target=server.serve_forever, daemon=True)
70+
thread.start()
71+
return server, f"http://127.0.0.1:{server.server_address[1]}", requests
72+
73+
74+
def _scrape_params() -> StartScrapeJobParams:
75+
return StartScrapeJobParams(
76+
url="https://example.com",
77+
scrape_options=ScrapeOptions(formats=["markdown"]),
78+
)
79+
80+
81+
def test_sync_client_uses_configured_api_endpoint_and_parses_responses():
82+
server, base_url, requests = _start_server()
83+
client = Hyperbrowser(api_key="test-api-key", base_url=base_url)
84+
try:
85+
started = client.scrape.start(_scrape_params())
86+
status = client.scrape.get_status(started.job_id)
87+
finally:
88+
client.close()
89+
server.shutdown()
90+
server.server_close()
91+
92+
assert started.job_id == "job_123"
93+
assert status.status == "completed"
94+
assert requests == [
95+
{
96+
"method": "POST",
97+
"path": "/api/scrape",
98+
"api_key": "test-api-key",
99+
"content_type": "application/json",
100+
"body": {
101+
"url": "https://example.com",
102+
"scrapeOptions": {
103+
"formats": ["markdown"],
104+
},
105+
},
106+
},
107+
{
108+
"method": "GET",
109+
"path": "/api/scrape/job_123/status",
110+
"api_key": "test-api-key",
111+
"content_type": None,
112+
"body": None,
113+
},
114+
]
115+
116+
117+
@pytest.mark.anyio
118+
async def test_async_client_uses_configured_api_endpoint_and_parses_responses():
119+
server, base_url, requests = _start_server()
120+
client = AsyncHyperbrowser(api_key="test-api-key", base_url=base_url)
121+
try:
122+
started = await client.scrape.start(_scrape_params())
123+
status = await client.scrape.get_status(started.job_id)
124+
finally:
125+
await client.close()
126+
server.shutdown()
127+
server.server_close()
128+
129+
assert started.job_id == "job_123"
130+
assert status.status == "completed"
131+
assert requests == [
132+
{
133+
"method": "POST",
134+
"path": "/api/scrape",
135+
"api_key": "test-api-key",
136+
"content_type": "application/json",
137+
"body": {
138+
"url": "https://example.com",
139+
"scrapeOptions": {
140+
"formats": ["markdown"],
141+
},
142+
},
143+
},
144+
{
145+
"method": "GET",
146+
"path": "/api/scrape/job_123/status",
147+
"api_key": "test-api-key",
148+
"content_type": None,
149+
"body": None,
150+
},
151+
]

0 commit comments

Comments
 (0)