Skip to content

Commit

Permalink
Implementation of SauceLabs job auth options (#147)
Browse files Browse the repository at this point in the history
  • Loading branch information
BeyondEvil authored and davehunt committed May 22, 2018
1 parent 9db9654 commit 0f474e4
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 2 deletions.
26 changes: 26 additions & 0 deletions docs/user_guide.rst
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,32 @@ the ``--capability`` command line arguments. See the
`test configuration documentation <https://docs.saucelabs.com/reference/test-configuration/>`_
for full details of what can be configured.

Test result links
~~~~~~~~~~~~~~~~~

By default, links to Sauce Labs jobs are only visible to users logged in to the account
that ran the job. To make a job visible without having to log in, you can create a link
with an authentication token.

This can be configured by setting the ``SAUCELABS_JOB_AUTH`` environment variable or by
using a :ref:`configuration file <configuration-files>`

An example using a :ref:`configuration file <configuration-files>`:

.. code-block:: ini
[pytest]
saucelabs_job_auth = token
You can also control the time to live for that link by setting the environment variable
or :ref:`configuration file <configuration-files>`: value to ``day`` or ``hour``.

Note that ``day`` means within the same day that the test was run,
*not* "24 hours from test-run", likewise for ``hour``

For more information, see
`building links to test results <https://wiki.saucelabs.com/display/DOCS/Building+Links+to+Test+Results>`_

BrowserStack
------------

Expand Down
39 changes: 37 additions & 2 deletions pytest_selenium/drivers/saucelabs.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class SauceLabs(Provider):

@property
def auth(self):
return (self.username, self.key)
return self.username, self.key

@property
def executor(self):
Expand Down Expand Up @@ -64,7 +64,7 @@ def pytest_selenium_runtest_makereport(item, report, summary, extra):

# Add the job URL to the summary
provider = SauceLabs()
job_url = provider.JOB.format(session=session_id)
job_url = get_job_url(item.config, provider, session_id)
summary.append('{0} Job: {1}'.format(provider.name, job_url))
pytest_html = item.config.pluginmanager.getplugin('html')
# Add the job URL to the HTML report
Expand Down Expand Up @@ -145,3 +145,38 @@ def _video_html(session):
id='player{session}'.format(session=session),
style='border:1px solid #e6e6e6; float:right; height:240px;'
'margin-left:5px; overflow:hidden; width:320px'))


def get_job_url(config, provider, session_id):
from datetime import datetime

job_url = provider.JOB.format(session=session_id)
job_auth = config.getini('saucelabs_job_auth').lower()

if job_auth == 'none':
return job_url

if job_auth == 'token':
return get_auth_url(job_url, provider, session_id)
elif job_auth == 'hour':
time_format = '%Y-%m-%d-%H'
elif job_auth == 'day':
time_format = '%Y-%m-%d'
else:
raise ValueError("Invalid authorization type: {}".format(job_auth))

ttl = datetime.utcnow().strftime(time_format)
return get_auth_url(job_url, provider, session_id, ttl)


def get_auth_url(url, provider, session_id, ttl=None):
import hmac
from hashlib import md5

key = '{0.username}:{0.key}'.format(provider)
if ttl:
key += ':{}'.format(ttl)
token = hmac.new(key.encode('utf-8'),
session_id.encode('utf-8'),
md5).hexdigest()
return '{}?auth={}'.format(url, token)
6 changes: 6 additions & 0 deletions pytest_selenium/pytest_selenium.py
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,12 @@ def pytest_addoption(parser):
help='debug to exclude from capture',
default=os.getenv('SELENIUM_EXCLUDE_DEBUG'))

_auth_choices = ('none', 'token', 'hour', 'day')
parser.addini('saucelabs_job_auth',
help='Authorization options for the Sauce Labs job: {0}'.
format(_auth_choices),
default=os.getenv('SAUCELABS_JOB_AUTH', 'none'))

group = parser.getgroup('selenium', 'selenium')
group._addoption('--driver',
action=DriverAction,
Expand Down
45 changes: 45 additions & 0 deletions testing/test_saucelabs.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@

from functools import partial
import os
import datetime

import pytest

monkeytime = datetime.datetime(2020, 12, 25, 17)

pytestmark = pytestmark = [pytest.mark.skip_selenium,
pytest.mark.nondestructive]

Expand Down Expand Up @@ -72,3 +75,45 @@ def test_invalid_credentials_file(failure, monkeypatch, tmpdir):
out = failure()
messages = ['Sauce Labs Authentication Error', 'basic auth failed']
assert any(message in out for message in messages)


@pytest.mark.parametrize(('auth_type', 'auth_token'),
[('none', ''),
('token', '5d5b3d4fe1fcb72edf5f8c431eb10175'),
('day', 'e9480f85e8183e94e39fc69af685831c'),
('hour', '441bac7116b1eeb026fed2a0942aad57')])
def test_auth_token(monkeypatch, auth_type, auth_token):
import datetime
from pytest_selenium.drivers.saucelabs import SauceLabs, get_job_url

monkeypatch.setattr(datetime, 'datetime', MonkeyDatetime)
monkeypatch.setenv('SAUCELABS_USERNAME', 'foo')
monkeypatch.setenv('SAUCELABS_API_KEY', 'bar')

session_id = '12345678'
url = 'http://saucelabs.com/jobs/{}'.format(session_id)

if auth_type != 'none':
url += '?auth={}'.format(auth_token)

result = get_job_url(Config(auth_type), SauceLabs(), session_id)
assert result == url


class MonkeyDatetime:

@classmethod
def utcnow(cls):
return monkeytime


class Config(object):

def __init__(self, value):
self._value = value

def getini(self, key):
if key == 'saucelabs_job_auth':
return self._value
else:
raise KeyError

0 comments on commit 0f474e4

Please sign in to comment.