Skip to content

Commit 37cc758

Browse files
authored
Merge pull request #221 from seleniumbase/proxy-with-auth
Ability to use Proxy servers that require auth
2 parents 2011a15 + 3b0a2b8 commit 37cc758

File tree

16 files changed

+195
-24
lines changed

16 files changed

+195
-24
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ report.html
6262

6363
# Other
6464
selenium-server-standalone.jar
65+
proxy.zip
6566
verbose_hub_server.dat
6667
verbose_node_server.dat
6768
ip_of_grid_hub.dat

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,12 @@ If you wish to use a proxy server for your browser tests (Chrome and Firefox onl
336336
pytest proxy_test.py --proxy=IP_ADDRESS:PORT
337337
```
338338

339+
If the proxy server that you wish to use requires authentication, you can do the following (Chrome only):
340+
341+
```
342+
pytest proxy_test.py --proxy=USERNAME:PASSWORD@IP_ADDRESS:PORT
343+
```
344+
339345
To make things easier, you can add your frequently-used proxies to PROXY_LIST in [proxy_list.py](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/config/proxy_list.py), and then use ``--proxy=KEY_FROM_PROXY_LIST`` to use the IP_ADDRESS:PORT of that key.
340346

341347
```

examples/my_first_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
class MyTestClass(BaseCase):
55

66
def test_basic(self):
7-
self.open('https://xkcd.com/353/') # Navigate to the web page
7+
self.open('https://xkcd.com/353/') # Navigate to the web page
88
self.assert_element('img[alt="Python"]') # Assert element on page
99
self.click('a[rel="license"]') # Click element on page
1010
self.assert_text('free to copy', 'div center') # Assert text on page

examples/proxy_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,6 @@ def test_proxy(self):
1010
self.open('https://ipinfo.io/%s' % ip_address)
1111
print("\n\nIP Address = %s\n" % ip_address)
1212
print("Displaying Host Info:")
13-
print(self.get_text('table.table'))
13+
print(self.get_text('ul.address-list'))
1414
print("\nThe browser will close automatically in 7 seconds...")
1515
time.sleep(7)

examples/tour_examples/xkcd_tour.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,5 @@ def test_basic(self):
1717
self.add_tour_step("Click for the license here.", 'a[rel="license"]')
1818
self.add_tour_step("This selects a random comic.", 'a[href*="random"]')
1919
self.add_tour_step("Thanks for taking this tour!")
20-
# self.export_tour() # Use this to export the tour as a .js file
20+
# self.export_tour() # Use this to export the tour as [my_tour.js]
2121
self.play_tour()
22-

help_docs/customizing_test_runs.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ pytest my_test_suite.py --server=IP_ADDRESS --port=4444
3636

3737
pytest my_test_suite.py --proxy=IP_ADDRESS:PORT
3838

39+
pytest my_test_suite.py --proxy=USERNAME:PASSWORD@IP_ADDRESS:PORT
40+
3941
pytest test_fail.py -s --pdb --pdb-failures
4042
```
4143

help_docs/features_list.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
* Uses a [global config file](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/config/settings.py) for configuring SeleniumBase to your specific needs.
1212
* Backwards-compatible with [WebDriver](http://www.seleniumhq.org/projects/webdriver/). (Use ``self.driver`` anywhere.)
1313
* Can run tests through a proxy server. (Use ``--proxy=IP_ADDRESS:PORT``)
14+
* Can use an authenticated proxy server. (``--proxy=USERNAME:PASSWORD@IP_ADDRESS:PORT``)
1415
* Can handle Google Authenticator logins by using the [Python one-time password library](https://pyotp.readthedocs.io/en/latest/).
1516
* Includes a hybrid-automation solution called **[MasterQA](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/masterqa/ReadMe.md)** to speed up manual testing.
1617
* Includes integrations with [MySQL](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/core/testcase_manager.py), [Selenium Grid](https://github.com/seleniumbase/SeleniumBase/tree/master/seleniumbase/utilities/selenium_grid), [Google Cloud](https://github.com/seleniumbase/SeleniumBase/tree/master/integrations/google_cloud/ReadMe.md), [Amazon S3](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/plugins/s3_logging_plugin.py), and [NodeJS](https://github.com/seleniumbase/SeleniumBase/tree/master/integrations/node_js).

requirements.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ pip
22
ipython
33
setuptools
44
selenium==3.14.1
5-
pytest>=3.8.2
5+
pytest>=3.9.1
66
pytest-cov>=2.6.0
77
pytest-html>=1.19.0
88
pytest-rerunfailures>=4.2
@@ -14,8 +14,8 @@ pyotp>=2.2.6
1414
requests>=2.19.1
1515
unittest2>=1.1.0
1616
chardet>=3.0.4
17-
urllib3>=1.23
1817
boto>=2.49.0
18+
urllib3==1.23
1919
nose==1.3.7
2020
ipdb==0.11
2121
flake8==3.5.0

seleniumbase/config/proxy_list.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,14 @@
1515
Example proxies in PROXY_LIST below are not guaranteed to be active or secure.
1616
If you don't already have a proxy server to connect to,
1717
you can try finding one from one of following sites:
18+
* https://www.proxynova.com/proxy-server-list/port-8080/
1819
* https://www.us-proxy.org/
1920
* https://hidemy.name/en/proxy-list/?country=US&type=h#list
2021
* http://proxyservers.pro/proxy/list/protocol/http/country/US/
2122
"""
2223

2324
PROXY_LIST = {
24-
# "example1": "64.33.247.157:3128", # (Example) - set your own proxy here
25+
"example1": "104.248.122.30:8080", # (Example) - set your own proxy here
2526
"proxy1": None,
2627
"proxy2": None,
2728
"proxy3": None,

seleniumbase/core/browser_launcher.py

Lines changed: 76 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
import os
22
import re
33
import sys
4+
import threading
5+
import time
46
import warnings
57
from selenium import webdriver
68
from selenium.common.exceptions import WebDriverException
79
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
810
from seleniumbase.config import proxy_list
911
from seleniumbase.core import download_helper
12+
from seleniumbase.core import proxy_helper
1013
from seleniumbase.fixtures import constants
1114
from seleniumbase.fixtures import page_utils
1215
from seleniumbase import drivers # webdriver storage folder for SeleniumBase
1316
DRIVER_DIR = os.path.dirname(os.path.realpath(drivers.__file__))
17+
PROXY_ZIP_PATH = proxy_helper.PROXY_ZIP_PATH
1418
PLATFORM = sys.platform
1519
IS_WINDOWS = False
1620
LOCAL_CHROMEDRIVER = None
@@ -49,7 +53,31 @@ def make_driver_executable_if_not(driver_path):
4953
make_executable(driver_path)
5054

5155

52-
def _set_chrome_options(downloads_path, proxy_string):
56+
def _add_chrome_proxy_extension(
57+
chrome_options, proxy_string, proxy_user, proxy_pass):
58+
""" Implementation of https://stackoverflow.com/a/35293284 for
59+
https://stackoverflow.com/questions/12848327/
60+
(Run Selenium on a proxy server that requires authentication.)
61+
The retry_on_exception is only needed for multithreaded runs
62+
because proxy.zip is a common file shared between all tests
63+
in a single run. """
64+
if not "".join(sys.argv) == "-c":
65+
# Single-threaded
66+
proxy_helper.create_proxy_zip(proxy_string, proxy_user, proxy_pass)
67+
else:
68+
# Pytest multi-threaded test
69+
lock = threading.Lock()
70+
with lock:
71+
if not os.path.exists(PROXY_ZIP_PATH):
72+
proxy_helper.create_proxy_zip(
73+
proxy_string, proxy_user, proxy_pass)
74+
time.sleep(0.3)
75+
chrome_options.add_extension(PROXY_ZIP_PATH)
76+
return chrome_options
77+
78+
79+
def _set_chrome_options(
80+
downloads_path, proxy_string, proxy_auth, proxy_user, proxy_pass):
5381
chrome_options = webdriver.ChromeOptions()
5482
prefs = {
5583
"download.default_directory": downloads_path,
@@ -71,6 +99,10 @@ def _set_chrome_options(downloads_path, proxy_string):
7199
chrome_options.add_argument("--disable-translate")
72100
chrome_options.add_argument("--disable-web-security")
73101
if proxy_string:
102+
if proxy_auth:
103+
chrome_options = _add_chrome_proxy_extension(
104+
chrome_options, proxy_string, proxy_user, proxy_pass)
105+
chrome_options.add_extension(DRIVER_DIR + "/proxy.zip")
74106
chrome_options.add_argument('--proxy-server=%s' % proxy_string)
75107
if "win32" in sys.platform or "win64" in sys.platform:
76108
chrome_options.add_argument("--log-level=3")
@@ -155,22 +187,55 @@ def validate_proxy_string(proxy_string):
155187

156188
def get_driver(browser_name, headless=False, use_grid=False,
157189
servername='localhost', port=4444, proxy_string=None):
190+
proxy_auth = False
191+
proxy_user = None
192+
proxy_pass = None
158193
if proxy_string:
194+
username_and_password = None
195+
if "@" in proxy_string:
196+
# Format => username:password@hostname:port
197+
try:
198+
username_and_password = proxy_string.split('@')[0]
199+
proxy_string = proxy_string.split('@')[1]
200+
proxy_user = username_and_password.split(':')[0]
201+
proxy_pass = username_and_password.split(':')[1]
202+
except Exception:
203+
raise Exception(
204+
'The format for using a proxy server with authentication '
205+
'is: "username:password@hostname:port". If using a proxy '
206+
'server without auth, the format is: "hostname:port".')
207+
if browser_name != constants.Browser.GOOGLE_CHROME:
208+
raise Exception(
209+
"Chrome is required when using a proxy server that has "
210+
"authentication! (If using a proxy server without auth, "
211+
"either Chrome or Firefox may be used.)")
159212
proxy_string = validate_proxy_string(proxy_string)
213+
if proxy_string and proxy_user and proxy_pass:
214+
if not os.path.exists(PROXY_ZIP_PATH):
215+
proxy_helper.create_proxy_zip(
216+
proxy_string, proxy_user, proxy_pass)
217+
proxy_auth = True
160218
if use_grid:
161219
return get_remote_driver(
162-
browser_name, headless, servername, port, proxy_string)
220+
browser_name, headless, servername, port, proxy_string, proxy_auth,
221+
proxy_user, proxy_pass)
163222
else:
164-
return get_local_driver(browser_name, headless, proxy_string)
223+
return get_local_driver(
224+
browser_name, headless, proxy_string, proxy_auth,
225+
proxy_user, proxy_pass)
165226

166227

167-
def get_remote_driver(browser_name, headless, servername, port, proxy_string):
228+
def get_remote_driver(
229+
browser_name, headless, servername, port, proxy_string, proxy_auth,
230+
proxy_user, proxy_pass):
168231
downloads_path = download_helper.get_downloads_folder()
169232
download_helper.reset_downloads_folder()
170233
address = "http://%s:%s/wd/hub" % (servername, port)
171234

172235
if browser_name == constants.Browser.GOOGLE_CHROME:
173-
chrome_options = _set_chrome_options(downloads_path, proxy_string)
236+
chrome_options = _set_chrome_options(
237+
downloads_path, proxy_string, proxy_auth,
238+
proxy_user, proxy_pass)
174239
if headless:
175240
chrome_options.add_argument("--headless")
176241
chrome_options.add_argument("--disable-gpu")
@@ -237,7 +302,9 @@ def get_remote_driver(browser_name, headless, servername, port, proxy_string):
237302
webdriver.DesiredCapabilities.PHANTOMJS))
238303

239304

240-
def get_local_driver(browser_name, headless, proxy_string):
305+
def get_local_driver(
306+
browser_name, headless, proxy_string, proxy_auth,
307+
proxy_user, proxy_pass):
241308
'''
242309
Spins up a new web browser and returns the driver.
243310
Can also be used to spin up additional browsers for the same test.
@@ -326,7 +393,9 @@ def get_local_driver(browser_name, headless, proxy_string):
326393
return webdriver.PhantomJS()
327394
elif browser_name == constants.Browser.GOOGLE_CHROME:
328395
try:
329-
chrome_options = _set_chrome_options(downloads_path, proxy_string)
396+
chrome_options = _set_chrome_options(
397+
downloads_path, proxy_string, proxy_auth,
398+
proxy_user, proxy_pass)
330399
if headless:
331400
chrome_options.add_argument("--headless")
332401
chrome_options.add_argument("--disable-gpu")

0 commit comments

Comments
 (0)