Skip to content

Commit 1bd4b21

Browse files
committed
feat: add playwright proxy formatter
1 parent c377c0b commit 1bd4b21

File tree

4 files changed

+262
-8
lines changed

4 files changed

+262
-8
lines changed

proxyproviders/models/proxy.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ def format(
112112
ProxyFormat.CURL: lambda: ["-x", self.to_url("http")],
113113
ProxyFormat.HTTPX: lambda: self._format_httpx(),
114114
ProxyFormat.AIOHTTP: lambda: self.to_url("http"),
115-
ProxyFormat.PLAYWRIGHT: lambda: self._format_playwright(),
115+
ProxyFormat.PLAYWRIGHT: lambda: self._format_playwright(**kwargs),
116116
}
117117

118118
handler = format_handlers.get(format_type)
@@ -132,9 +132,18 @@ def _format_httpx(self):
132132
proxy_url = self.to_url("http")
133133
return {"http://": proxy_url, "https://": proxy_url}
134134

135-
def _format_playwright(self):
135+
def _format_playwright(self, **kwargs):
136136
"""Format proxy for Playwright."""
137-
playwright_proxy = {"server": f"{self.proxy_address}:{self.port}"}
137+
# Playwright expects server with protocol (e.g., 'http://ip:port', 'socks5://ip:port')
138+
# Allow protocol selection via kwargs, default to http
139+
protocol = kwargs.get("protocol", "http")
140+
141+
# Validate protocol is supported by the proxy
142+
if self.protocols and protocol not in self.protocols:
143+
# If proxy has specific protocols, use the first available one
144+
protocol = self.protocols[0]
145+
146+
playwright_proxy = {"server": f"{protocol}://{self.proxy_address}:{self.port}"}
138147

139148
if self.username and self.password:
140149
playwright_proxy["username"] = self.username

tests/integration/test_algorithms_integration.py

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,119 @@ def test_webshare_proxy_conversion_methods():
9696
assert "https" in requests_dict
9797
print(f"Requests dict: {requests_dict}")
9898

99+
# Test Playwright format (default protocol)
100+
playwright_dict = proxy.format(ProxyFormat.PLAYWRIGHT)
101+
assert isinstance(playwright_dict, dict)
102+
assert "server" in playwright_dict
103+
assert playwright_dict["server"].startswith("http://")
104+
assert f"{proxy.proxy_address}:{proxy.port}" in playwright_dict["server"]
105+
assert "username" in playwright_dict
106+
assert "password" in playwright_dict
107+
assert playwright_dict["username"] == proxy.username
108+
assert playwright_dict["password"] == proxy.password
109+
110+
# Test Playwright format with different protocols
111+
for protocol in ["http", "https"]:
112+
playwright_protocol = proxy.format(ProxyFormat.PLAYWRIGHT, protocol=protocol)
113+
assert playwright_protocol["server"].startswith(f"{protocol}://")
114+
115+
116+
@skip_integration
117+
def test_webshare_playwright_format_comprehensive():
118+
"""Comprehensive test of Playwright format with real Webshare API."""
119+
api_key = os.getenv("WEBSHARE_API_KEY")
120+
provider = Webshare(api_key=api_key)
121+
122+
proxy = provider.get_proxy()
123+
124+
# Test 1: Default Playwright format
125+
default_format = proxy.format(ProxyFormat.PLAYWRIGHT)
126+
assert isinstance(default_format, dict)
127+
assert "server" in default_format
128+
assert "username" in default_format
129+
assert "password" in default_format
130+
assert default_format["server"].startswith("http://")
131+
132+
# Test 2: Protocol variations
133+
protocols_to_test = ["http", "https"]
134+
135+
for protocol in protocols_to_test:
136+
result = proxy.format(ProxyFormat.PLAYWRIGHT, protocol=protocol)
137+
assert result["server"].startswith(f"{protocol}://")
138+
assert result["username"] == proxy.username
139+
assert result["password"] == proxy.password
140+
141+
# Test 3: String format
142+
string_format = proxy.format("playwright")
143+
assert string_format == default_format
144+
145+
# Test 4: Format consistency
146+
enum_format = proxy.format(ProxyFormat.PLAYWRIGHT)
147+
string_format = proxy.format("playwright")
148+
assert enum_format == string_format
149+
150+
# Test 5: Playwright documentation compliance
151+
playwright_config = proxy.format(ProxyFormat.PLAYWRIGHT)
152+
153+
# Verify structure matches Playwright docs
154+
required_fields = ["server"]
155+
optional_fields = ["username", "password"]
156+
157+
for field in required_fields:
158+
assert field in playwright_config, f"Missing required field: {field}"
159+
160+
for field in optional_fields:
161+
if field in playwright_config:
162+
assert True # Field is present
163+
else:
164+
assert False, f"Missing optional field: {field}"
165+
166+
# Verify server format
167+
server = playwright_config["server"]
168+
assert "://" in server, "Server should include protocol"
169+
assert (
170+
f"{proxy.proxy_address}:{proxy.port}" in server
171+
), "Server should include address and port"
172+
173+
174+
@skip_integration
175+
def test_webshare_playwright_e2e_simulation():
176+
"""End-to-end simulation of Playwright usage with real Webshare proxy."""
177+
api_key = os.getenv("WEBSHARE_API_KEY")
178+
provider = Webshare(api_key=api_key)
179+
180+
proxy = provider.get_proxy()
181+
182+
# Convert to Playwright format
183+
playwright_config = proxy.format(ProxyFormat.PLAYWRIGHT)
184+
185+
# Verify the format is exactly what Playwright expects
186+
# Check server format
187+
server = playwright_config["server"]
188+
assert "://" in server, "Server must include protocol"
189+
assert (
190+
f"{proxy.proxy_address}:{proxy.port}" in server
191+
), "Server must include address and port"
192+
193+
# Check authentication
194+
assert "username" in playwright_config, "Username field is required"
195+
assert "password" in playwright_config, "Password field is required"
196+
197+
# Check required fields
198+
assert "server" in playwright_config, "Server field is required"
199+
200+
# Check optional fields
201+
optional_fields = ["username", "password"]
202+
for field in optional_fields:
203+
assert field in playwright_config, f"Missing optional field: {field}"
204+
205+
# Test different protocols
206+
protocols = ["http", "https"]
207+
for protocol in protocols:
208+
config = proxy.format(ProxyFormat.PLAYWRIGHT, protocol=protocol)
209+
server = config["server"]
210+
assert server.startswith(f"{protocol}://")
211+
99212

100213
@skip_integration
101214
def test_webshare_e2e_with_requests():

tests/integration/test_brightdata_algorithms_integration.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,86 @@ def test_brightdata_proxy_conversion_methods():
9494
assert "https" in requests_dict
9595
print(f"BrightData Requests dict: {requests_dict}")
9696

97+
# Test Playwright format (default protocol)
98+
playwright_dict = proxy.format(ProxyFormat.PLAYWRIGHT)
99+
assert isinstance(playwright_dict, dict)
100+
assert "server" in playwright_dict
101+
assert playwright_dict["server"].startswith("http://")
102+
assert f"{proxy.proxy_address}:{proxy.port}" in playwright_dict["server"]
103+
assert "username" in playwright_dict
104+
assert "password" in playwright_dict
105+
assert playwright_dict["username"] == proxy.username
106+
assert playwright_dict["password"] == proxy.password
107+
108+
# Test Playwright format with different protocols
109+
for protocol in ["http", "https"]:
110+
playwright_protocol = proxy.format(ProxyFormat.PLAYWRIGHT, protocol=protocol)
111+
assert playwright_protocol["server"].startswith(f"{protocol}://")
112+
113+
114+
@skip_integration
115+
def test_brightdata_playwright_format_comprehensive():
116+
"""Comprehensive test of Playwright format with real BrightData API."""
117+
api_key = os.getenv("BRIGHTDATA_API_KEY")
118+
provider = BrightData(api_key=api_key, zone="static")
119+
120+
proxy = provider.get_proxy()
121+
122+
# Test 1: Default Playwright format
123+
default_format = proxy.format(ProxyFormat.PLAYWRIGHT)
124+
assert isinstance(default_format, dict)
125+
assert "server" in default_format
126+
assert "username" in default_format
127+
assert "password" in default_format
128+
assert default_format["server"].startswith("http://")
129+
130+
# Test 2: Protocol variations
131+
protocols_to_test = ["http", "https"]
132+
133+
for protocol in protocols_to_test:
134+
result = proxy.format(ProxyFormat.PLAYWRIGHT, protocol=protocol)
135+
assert result["server"].startswith(f"{protocol}://")
136+
assert result["username"] == proxy.username
137+
assert result["password"] == proxy.password
138+
139+
# Test 3: String format
140+
string_format = proxy.format("playwright")
141+
assert string_format == default_format
142+
143+
# Test 4: Format consistency
144+
enum_format = proxy.format(ProxyFormat.PLAYWRIGHT)
145+
string_format = proxy.format("playwright")
146+
assert enum_format == string_format
147+
148+
# Test 5: Playwright documentation compliance
149+
playwright_config = proxy.format(ProxyFormat.PLAYWRIGHT)
150+
151+
# Verify structure matches Playwright docs
152+
required_fields = ["server"]
153+
optional_fields = ["username", "password"]
154+
155+
for field in required_fields:
156+
assert field in playwright_config, f"Missing required field: {field}"
157+
158+
for field in optional_fields:
159+
if field in playwright_config:
160+
assert True # Field is present
161+
else:
162+
assert False, f"Missing optional field: {field}"
163+
164+
# Verify server format
165+
server = playwright_config["server"]
166+
assert "://" in server, "Server should include protocol"
167+
assert (
168+
f"{proxy.proxy_address}:{proxy.port}" in server
169+
), "Server should include address and port"
170+
171+
# Test 6: Protocol fallback behavior
172+
# Try to use an unsupported protocol - should fallback gracefully
173+
fallback_result = proxy.format(ProxyFormat.PLAYWRIGHT, protocol="socks5")
174+
assert "server" in fallback_result
175+
assert fallback_result["server"].startswith("http://") # Should fallback to http
176+
97177

98178
@skip_integration
99179
def test_brightdata_e2e_with_requests():

tests/test_proxy_model.py

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ def test_format_playwright_with_auth(self, sample_proxy):
145145
"""Test format for Playwright with authentication."""
146146
result = sample_proxy.format(ProxyFormat.PLAYWRIGHT)
147147
expected = {
148-
"server": "192.168.1.100:8080",
148+
"server": "http://192.168.1.100:8080",
149149
"username": "testuser",
150150
"password": "testpass",
151151
}
@@ -155,7 +155,7 @@ def test_format_playwright_without_auth(self, sample_proxy_no_protocols):
155155
"""Test format for Playwright with authentication (sample_proxy_no_protocols has auth)."""
156156
result = sample_proxy_no_protocols.format(ProxyFormat.PLAYWRIGHT)
157157
expected = {
158-
"server": "10.0.0.1:3128",
158+
"server": "http://10.0.0.1:3128",
159159
"username": "user2",
160160
"password": "pass2",
161161
}
@@ -171,7 +171,7 @@ def test_format_playwright_empty_credentials(self):
171171
port=8080,
172172
)
173173
result = proxy.format(ProxyFormat.PLAYWRIGHT)
174-
expected = {"server": "192.168.1.1:8080"}
174+
expected = {"server": "http://192.168.1.1:8080"}
175175
assert result == expected
176176

177177
def test_format_playwright_none_credentials(self):
@@ -184,19 +184,71 @@ def test_format_playwright_none_credentials(self):
184184
port=8080,
185185
)
186186
result = proxy.format(ProxyFormat.PLAYWRIGHT)
187-
expected = {"server": "192.168.1.1:8080"}
187+
expected = {"server": "http://192.168.1.1:8080"}
188188
assert result == expected
189189

190190
def test_format_playwright_string_format(self, sample_proxy):
191191
"""Test format for Playwright using string format."""
192192
result = sample_proxy.format("playwright")
193193
expected = {
194-
"server": "192.168.1.100:8080",
194+
"server": "http://192.168.1.100:8080",
195195
"username": "testuser",
196196
"password": "testpass",
197197
}
198198
assert result == expected
199199

200+
def test_format_playwright_with_protocol(self, sample_proxy):
201+
"""Test format for Playwright with specific protocol."""
202+
# Test with https protocol
203+
result = sample_proxy.format(ProxyFormat.PLAYWRIGHT, protocol="https")
204+
expected = {
205+
"server": "https://192.168.1.100:8080",
206+
"username": "testuser",
207+
"password": "testpass",
208+
}
209+
assert result == expected
210+
211+
def test_format_playwright_with_socks5(self):
212+
"""Test format for Playwright with SOCKS5 protocol."""
213+
# Create a proxy that supports SOCKS5
214+
proxy = Proxy(
215+
id="socks5-test",
216+
username="testuser",
217+
password="testpass",
218+
proxy_address="192.168.1.100",
219+
port=8080,
220+
protocols=["http", "https", "socks5"], # Include socks5 support
221+
)
222+
223+
result = proxy.format(ProxyFormat.PLAYWRIGHT, protocol="socks5")
224+
expected = {
225+
"server": "socks5://192.168.1.100:8080",
226+
"username": "testuser",
227+
"password": "testpass",
228+
}
229+
assert result == expected
230+
231+
def test_format_playwright_protocol_fallback(self):
232+
"""Test format for Playwright with protocol fallback."""
233+
# Create proxy with limited protocols
234+
proxy = Proxy(
235+
id="limited-protocol",
236+
username="user",
237+
password="pass",
238+
proxy_address="192.168.1.1",
239+
port=8080,
240+
protocols=["http"], # Only supports HTTP
241+
)
242+
243+
# Request unsupported protocol, should fallback to http
244+
result = proxy.format(ProxyFormat.PLAYWRIGHT, protocol="socks5")
245+
expected = {
246+
"server": "http://192.168.1.1:8080",
247+
"username": "user",
248+
"password": "pass",
249+
}
250+
assert result == expected
251+
200252
def test_format_string_enum(self, sample_proxy):
201253
"""Test format with string instead of enum."""
202254
result = sample_proxy.format("requests")

0 commit comments

Comments
 (0)