Skip to content

Commit 2b3d913

Browse files
committed
Move sensitive configuration options into separate files
Read credentials for cloud testing services from configuration files in the working or home directory. Fixes #60
1 parent 39cffc1 commit 2b3d913

12 files changed

+366
-223
lines changed

docs/news.rst

+26
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,32 @@
11
Release Notes
22
=============
33

4+
**1.8.0 (unreleased)**
5+
6+
* **BREAKING CHANGE:** Moved cloud testing provider credentials into separate
7+
files for improved security.
8+
9+
* If you are using the environment variables for specifying cloud testing
10+
provider credentials, then you will not be affected.
11+
* If you are storing credentials from any of the cloud testing providers in
12+
one of the default configuration files then they will no longer be used.
13+
These files are often checked into source code repositories, so it was
14+
previously very easy to accidentally expose your credentials.
15+
* Each cloud provider now has their own configuration file, such as
16+
``.browserstack``, ``.crossbrowsertesting``, ``.saucelabs``,
17+
``.testingbot`` and these can be located in the working directory or in the
18+
user's home directory. This provides a convenient way to set up these files
19+
globally, and override them for individual projects.
20+
* To migrate, check ``pytest.ini``, ``tox.ini``, and ``setup.cfg`` for any
21+
keys starting with ``browserstack_``, ``crossbrowsertesting_``,
22+
``saucelabs_``, or ``testingbot_``. If you find any, create a new
23+
configuration file for the appropriate cloud testing provider with your
24+
credentials, and remove the entries from the original file.
25+
* The configuration keys can differ between cloud testing providers, so
26+
please check the :doc:`user_guide` for details.
27+
* See `#60 <https://github.com/pytest-dev/pytest-selenium/issues/60>`_ for
28+
for original issue and related patch.
29+
430
**1.7.0 (2016-11-29)**
531

632
* Introduced a ``firefox_options`` fixture.

docs/user_guide.rst

+30-27
Original file line numberDiff line numberDiff line change
@@ -218,19 +218,20 @@ Sauce Labs
218218

219219
To run your automated tests using `Sauce Labs <https://saucelabs.com/>`_, you
220220
must provide a valid username and API key. This can be done either by using
221-
a :ref:`configuration file <configuration-files>`, or by setting the
222-
``SAUCELABS_USERNAME`` and ``SAUCELABS_API_KEY`` environment variables.
221+
a ``.saucelabs`` configuration file in the working directory or your home
222+
directory, or by setting the ``SAUCELABS_USERNAME`` and ``SAUCELABS_API_KEY``
223+
environment variables.
223224

224225
Configuration
225226
~~~~~~~~~~~~~
226227

227-
Below is an example :ref:`configuration file <configuration-files>`:
228+
Below is an example ``.saucelabs`` configuration file:
228229

229230
.. code-block:: ini
230231
231-
[pytest]
232-
sauce_labs_username = username
233-
sauce_labs_api_key = secret
232+
[credentials]
233+
username = username
234+
key = secret
234235
235236
Running tests
236237
~~~~~~~~~~~~~
@@ -252,20 +253,21 @@ BrowserStack
252253

253254
To run your automated tests using
254255
`BrowserStack <https://www.browserstack.com/>`_, you must provide a valid
255-
username and access key. This can be done either by using a
256-
:ref:`configuration file <configuration-files>`, or by setting the
257-
``BROWSERSTACK_USERNAME`` and ``BROWSERSTACK_ACCESS_KEY`` environment variables.
256+
username and access key. This can be done either by using
257+
a ``.browserstack`` configuration file in the working directory or your home
258+
directory, or by setting the ``BROWSERSTACK_USERNAME`` and
259+
``BROWSERSTACK_ACCESS_KEY`` environment variables.
258260

259261
Configuration
260262
~~~~~~~~~~~~~
261263

262-
Below is an example :ref:`configuration file <configuration-files>`:
264+
Below is an example ``.browserstack`` configuration file:
263265

264266
.. code-block:: ini
265267
266-
[pytest]
267-
browserstack_username = username
268-
browserstack_access_key = secret
268+
[credentials]
269+
username = username
270+
key = secret
269271
270272
Running tests
271273
~~~~~~~~~~~~~
@@ -285,20 +287,21 @@ TestingBot
285287
----------
286288

287289
To run your automated tests using `TestingBot <http://testingbot.com/>`_, you
288-
must provide a valid key and secret. This can be done either by using a
289-
:ref:`configuration file <configuration-files>`, or by setting the
290-
``TESTINGBOT_KEY`` and ``TESTINGBOT_SECRET`` environment variables.
290+
must provide a valid key and secret. This can be done either by using
291+
a ``.testingbot`` configuration file in the working directory or your home
292+
directory, or by setting the ``TESTINGBOT_KEY`` and ``TESTINGBOT_SECRET``
293+
environment variables.
291294

292295
Configuration
293296
~~~~~~~~~~~~~
294297

295-
Below is an example :ref:`configuration file <configuration-files>`:
298+
Below is an example ``.testingbot`` configuration file:
296299

297300
.. code-block:: ini
298301
299-
[pytest]
300-
testingbot_key = key
301-
testingbot_secret = secret
302+
[credentials]
303+
key = key
304+
secret = secret
302305
303306
Running tests
304307
~~~~~~~~~~~~~
@@ -321,20 +324,20 @@ CrossBrowserTesting
321324
To run your automated tests using
322325
`CrossBrowserTesting <https://crossbrowsertesting.com/>`_, you must provide a
323326
valid username and auth key. This can be done either by using
324-
a :ref:`configuration file <configuration-files>`, or by setting the
325-
``CROSSBROWSERTESTING_USERNAME`` and ``CROSSBROWSERTESTING_AUTH_KEY``
326-
environment variables.
327+
a ``.crossbrowsertesting`` configuration file in the working directory or your
328+
home directory, or by setting the ``CROSSBROWSERTESTING_USERNAME`` and
329+
``CROSSBROWSERTESTING_AUTH_KEY`` environment variables.
327330

328331
Configuration
329332
~~~~~~~~~~~~~
330333

331-
Below is an example :ref:`configuration file <configuration-files>`:
334+
Below is an example ``.crossbrowsertesting`` configuration file:
332335

333336
.. code-block:: ini
334337
335-
[pytest]
336-
crossbrowsertesting_username = username
337-
crossbrowsertesting_auth_key = secret
338+
[credentials]
339+
username = username
340+
key = secret
338341
339342
Running tests
340343
~~~~~~~~~~~~~

pytest_selenium/drivers/browserstack.py

+32-38
Original file line numberDiff line numberDiff line change
@@ -2,46 +2,56 @@
22
# License, v. 2.0. If a copy of the MPL was not distributed with this
33
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
44

5-
import os
6-
75
import pytest
86
import requests
97

10-
DRIVER = 'BrowserStack'
11-
API_JOB_URL = 'https://www.browserstack.com/automate/sessions/{session}.json'
12-
EXECUTOR_URL = 'http://{username}:{key}@hub.browserstack.com:80/wd/hub'
8+
from pytest_selenium.drivers.cloud import Provider
9+
10+
11+
class BrowserStack(Provider):
12+
13+
API = 'https://www.browserstack.com/automate/sessions/{session}.json'
14+
15+
@property
16+
def auth(self):
17+
return (self.username, self.key)
1318

19+
@property
20+
def executor(self):
21+
return 'http://{0}:{1}@hub.browserstack.com:80/wd/hub'.format(
22+
self.username, self.key)
1423

15-
def pytest_addoption(parser):
16-
parser.addini('browserstack_username',
17-
help='browserstack username',
18-
default=os.getenv('BROWSERSTACK_USERNAME'))
19-
parser.addini('browserstack_access_key',
20-
help='browserstack access key',
21-
default=os.getenv('BROWSERSTACK_ACCESS_KEY'))
24+
@property
25+
def username(self):
26+
return self.get_credential('username', 'BROWSERSTACK_USERNAME')
27+
28+
@property
29+
def key(self):
30+
return self.get_credential('key', 'BROWSERSTACK_ACCESS_KEY')
2231

2332

2433
@pytest.mark.optionalhook
2534
def pytest_selenium_runtest_makereport(item, report, summary, extra):
26-
if item.config.getoption('driver') != DRIVER:
35+
provider = BrowserStack()
36+
if item.config.getoption('driver') != provider.driver:
2737
return
2838

2939
passed = report.passed or (report.failed and hasattr(report, 'wasxfail'))
3040
session_id = item._driver.session_id
31-
auth = (_username(item.config), _access_key(item.config))
32-
api_url = API_JOB_URL.format(session=session_id)
41+
api_url = provider.API.format(session=session_id)
3342

3443
try:
35-
job_info = requests.get(api_url, auth=auth, timeout=10).json()
44+
job_info = requests.get(api_url, auth=provider.auth, timeout=10).json()
3645
job_url = job_info['automation_session']['browser_url']
3746
# Add the job URL to the summary
38-
summary.append('{0} Job: {1}'.format(DRIVER, job_url))
47+
summary.append('{0} Job: {1}'.format(provider.name, job_url))
3948
pytest_html = item.config.pluginmanager.getplugin('html')
4049
# Add the job URL to the HTML report
41-
extra.append(pytest_html.extras.url(job_url, '{0} Job'.format(DRIVER)))
50+
extra.append(pytest_html.extras.url(job_url, '{0} Job'.format(
51+
provider.name)))
4252
except Exception as e:
4353
summary.append('WARNING: Failed to determine {0} job URL: {1}'.format(
44-
DRIVER, e))
54+
provider.name, e))
4555

4656
try:
4757
# Update the job result
@@ -55,32 +65,16 @@ def pytest_selenium_runtest_makereport(item, report, summary, extra):
5565
api_url,
5666
headers={'Content-Type': 'application/json'},
5767
params={'status': status},
58-
auth=auth,
68+
auth=provider.auth,
5969
timeout=10)
6070
except Exception as e:
6171
summary.append('WARNING: Failed to update job status: {0}'.format(e))
6272

6373

6474
def driver_kwargs(request, test, capabilities, **kwargs):
75+
provider = BrowserStack()
6576
capabilities.setdefault('name', test)
66-
executor = EXECUTOR_URL.format(
67-
username=_username(request.config),
68-
key=_access_key(request.config))
6977
kwargs = {
70-
'command_executor': executor,
78+
'command_executor': provider.executor,
7179
'desired_capabilities': capabilities}
7280
return kwargs
73-
74-
75-
def _access_key(config):
76-
access_key = config.getini('browserstack_access_key')
77-
if not access_key:
78-
raise pytest.UsageError('BrowserStack access key must be set')
79-
return access_key
80-
81-
82-
def _username(config):
83-
username = config.getini('browserstack_username')
84-
if not username:
85-
raise pytest.UsageError('BrowserStack username must be set')
86-
return username

pytest_selenium/drivers/cloud.py

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# This Source Code Form is subject to the terms of the Mozilla Public
2+
# License, v. 2.0. If a copy of the MPL was not distributed with this
3+
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
4+
5+
import os
6+
import sys
7+
8+
from pytest_selenium.exceptions import MissingCloudCredentialError
9+
10+
if sys.version_info[0] == 2:
11+
import ConfigParser as configparser
12+
else:
13+
import configparser
14+
15+
16+
class Provider(object):
17+
18+
@property
19+
def driver(self):
20+
return type(self).__name__
21+
22+
@property
23+
def name(self):
24+
return self.driver
25+
26+
@property
27+
def config(self):
28+
name = '.{0}'.format(self.driver.lower())
29+
config = configparser.ConfigParser()
30+
config.read([name, os.path.join(os.path.expanduser('~'), name)])
31+
return config
32+
33+
def get_credential(self, key, env):
34+
try:
35+
value = self.config.get('credentials', key)
36+
except (configparser.NoSectionError,
37+
configparser.NoOptionError,
38+
KeyError):
39+
value = os.getenv(env)
40+
if not value:
41+
raise MissingCloudCredentialError(self.name, key, env)
42+
return value

0 commit comments

Comments
 (0)