browser.py 7.44 KB
Newer Older
Will Daly committed
1 2 3 4 5 6 7
"""
Browser set up for acceptance tests.
"""

#pylint: disable=E1101
#pylint: disable=W0613

8 9 10
from lettuce import before, after, world
from splinter.browser import Browser
from logging import getLogger
11 12
from django.core.management import call_command
from django.conf import settings
13
from selenium.common.exceptions import WebDriverException
14
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
15
import requests
16 17
from base64 import encodestring
from json import dumps
18

19 20
from pymongo import MongoClient
import xmodule.modulestore.django
21
from xmodule.contentstore.django import _CONTENTSTORE
22

23 24 25 26 27 28 29 30 31
# There is an import issue when using django-staticfiles with lettuce
# Lettuce assumes that we are using django.contrib.staticfiles,
# but the rest of the app assumes we are using django-staticfiles
# (in particular, django-pipeline and our mako implementation)
# To resolve this, we check whether staticfiles is installed,
# then redirect imports for django.contrib.staticfiles
# to use staticfiles.
try:
    import staticfiles
32
    import staticfiles.handlers
33 34 35 36 37
except ImportError:
    pass
else:
    import sys
    sys.modules['django.contrib.staticfiles'] = staticfiles
38
    sys.modules['django.contrib.staticfiles.handlers'] = staticfiles.handlers
39

Will Daly committed
40 41
LOGGER = getLogger(__name__)
LOGGER.info("Loading the lettuce acceptance testing terrain file...")
Calen Pennington committed
42

43
MAX_VALID_BROWSER_ATTEMPTS = 20
44
GLOBAL_SCRIPT_TIMEOUT = 20
45

46

47
def get_saucelabs_username_and_key():
48 49 50 51
    """
    Returns the Sauce Labs username and access ID as set by environment variables
    """
    return {"username": settings.SAUCE.get('USERNAME'), "access-key": settings.SAUCE.get('ACCESS_ID')}
52

53

54
def set_saucelabs_job_status(jobid, passed=True):
55 56 57
    """
    Sets the job status on sauce labs
    """
58
    config = get_saucelabs_username_and_key()
59 60
    url = 'http://saucelabs.com/rest/v1/{}/jobs/{}'.format(config['username'], world.jobid)
    body_content = dumps({"passed": passed})
61
    base64string = encodestring('{}:{}'.format(config['username'], config['access-key']))[:-1]
62 63
    headers = {"Authorization": "Basic {}".format(base64string)}
    result = requests.put(url, data=body_content, headers=headers)
64
    return result.status_code == 200
65

66

67
def make_saucelabs_desired_capabilities():
68 69 70 71
    """
    Returns a DesiredCapabilities object corresponding to the environment sauce parameters
    """
    desired_capabilities = settings.SAUCE.get('BROWSER', DesiredCapabilities.CHROME)
72 73 74 75 76 77 78
    desired_capabilities['platform'] = settings.SAUCE.get('PLATFORM')
    desired_capabilities['version'] = settings.SAUCE.get('VERSION')
    desired_capabilities['device-type'] = settings.SAUCE.get('DEVICE')
    desired_capabilities['name'] = settings.SAUCE.get('SESSION')
    desired_capabilities['build'] = settings.SAUCE.get('BUILD')
    desired_capabilities['video-upload-on-pass'] = False
    desired_capabilities['sauce-advisor'] = False
79 80
    desired_capabilities['capture-html'] = True
    desired_capabilities['record-screenshots'] = True
81 82 83 84 85
    desired_capabilities['selenium-version'] = "2.34.0"
    desired_capabilities['max-duration'] = 3600
    desired_capabilities['public'] = 'public restricted'
    return desired_capabilities

86

87 88
@before.harvest
def initial_setup(server):
Will Daly committed
89 90 91
    """
    Launch the browser once before executing the tests.
    """
92
    world.absorb(settings.LETTUCE_SELENIUM_CLIENT, 'LETTUCE_SELENIUM_CLIENT')
93

94
    if world.LETTUCE_SELENIUM_CLIENT == 'local':
95 96 97 98 99 100 101 102
        browser_driver = getattr(settings, 'LETTUCE_BROWSER', 'chrome')

        # There is an issue with ChromeDriver2 r195627 on Ubuntu
        # in which we sometimes get an invalid browser session.
        # This is a work-around to ensure that we get a valid session.
        success = False
        num_attempts = 0
        while (not success) and num_attempts < MAX_VALID_BROWSER_ATTEMPTS:
103

104 105 106
            # Load the browser and try to visit the main page
            # If the browser couldn't be reached or
            # the browser session is invalid, this will
107 108
            # raise a WebDriverException
            try:
109
                world.browser = Browser(browser_driver)
110
                world.browser.driver.set_script_timeout(GLOBAL_SCRIPT_TIMEOUT)
111
                world.visit('/')
112

113
            except WebDriverException:
114 115
                if hasattr(world, 'browser'):
                    world.browser.quit()
116
                num_attempts += 1
117

118 119
            else:
                success = True
120

121 122 123
        # If we were unable to get a valid session within the limit of attempts,
        # then we cannot run the tests.
        if not success:
JonahStanley committed
124
            raise IOError("Could not acquire valid {driver} browser session.".format(driver=browser_driver))
Will Daly committed
125

126
        world.absorb(0, 'IMPLICIT_WAIT')
127
        world.browser.driver.set_window_size(1280, 1024)
128

129 130
    elif world.LETTUCE_SELENIUM_CLIENT == 'saucelabs':
        config = get_saucelabs_username_and_key()
131 132 133
        world.browser = Browser(
            'remote',
            url="http://{}:{}@ondemand.saucelabs.com:80/wd/hub".format(config['username'], config['access-key']),
134 135
            **make_saucelabs_desired_capabilities()
        )
136
        world.absorb(30, 'IMPLICIT_WAIT')
137
        world.browser.set_script_timeout(GLOBAL_SCRIPT_TIMEOUT)
138 139 140 141 142 143

    elif world.LETTUCE_SELENIUM_CLIENT == 'grid':
        world.browser = Browser(
            'remote',
            url=settings.SELENIUM_GRID.get('URL'),
            browser=settings.SELENIUM_GRID.get('BROWSER'),
144
        )
145
        world.absorb(30, 'IMPLICIT_WAIT')
146
        world.browser.driver.set_script_timeout(GLOBAL_SCRIPT_TIMEOUT)
147

148 149 150
    else:
        raise Exception("Unknown selenium client '{}'".format(world.LETTUCE_SELENIUM_CLIENT))

151
    world.browser.driver.implicitly_wait(world.IMPLICIT_WAIT)
152 153
    world.absorb(world.browser.driver.session_id, 'jobid')

Will Daly committed
154

155 156
@before.each_scenario
def reset_data(scenario):
Will Daly committed
157
    """
158
    Clean out the django test database defined in the
159
    envs/acceptance.py file: edx-platform/db/test_edx.db
Will Daly committed
160 161
    """
    LOGGER.debug("Flushing the test database...")
162
    call_command('flush', interactive=False, verbosity=0)
163 164 165 166 167 168 169
    world.absorb({}, 'scenario_dict')


@after.each_scenario
def clear_data(scenario):
    world.spew('scenario_dict')

170

171 172
@after.each_scenario
def reset_databases(scenario):
173 174 175 176 177
    '''
    After each scenario, all databases are cleared/dropped.  Contentstore data are stored in unique databases
    whereas modulestore data is in unique collection names.  This data is created implicitly during the scenarios.
    If no data is created during the test, these lines equivilently do nothing.
    '''
178
    mongo = MongoClient()
179
    mongo.drop_database(settings.CONTENTSTORE['DOC_STORE_CONFIG']['db'])
180
    _CONTENTSTORE.clear()
181 182

    modulestore = xmodule.modulestore.django.editable_modulestore()
183
    modulestore.collection.drop()
184
    xmodule.modulestore.django.clear_existing_modulestores()
185 186


187
@after.each_scenario
188
def screenshot_on_error(scenario):
Will Daly committed
189 190 191
    """
    Save a screenshot to help with debugging.
    """
192
    if scenario.failed:
193 194 195 196 197 198
        try:
            output_dir = '{}/log'.format(settings.TEST_ROOT)
            image_name = '{}/{}.png'.format(output_dir, scenario.name.replace(' ', '_'))
            world.browser.driver.save_screenshot(image_name)
        except WebDriverException:
            LOGGER.error('Could not capture a screenshot')
199

200

201
@after.harvest
202
def teardown_browser(total):
Will Daly committed
203 204 205
    """
    Quit the browser after executing the tests.
    """
206 207
    if world.LETTUCE_SELENIUM_CLIENT == 'saucelabs':
        set_saucelabs_job_status(world.jobid, total.scenarios_ran == total.scenarios_passed)
208
    world.browser.quit()