common.py 11 KB
Newer Older
Don Mitchell committed
1 2
# pylint: disable=C0111
# pylint: disable=W0621
3

4
from lettuce import world, step
5
from nose.tools import assert_true, assert_in, assert_false  # pylint: disable=E0611
6

7
from auth.authz import get_user_by_email, get_course_groupname_for_role
Peter Fogg committed
8
from django.conf import settings
9

cahrens committed
10 11
from selenium.webdriver.common.keys import Keys
import time
Peter Fogg committed
12
import os
13
from django.contrib.auth.models import Group
cahrens committed
14

15 16 17
from logging import getLogger
logger = getLogger(__name__)

18 19
from terrain.browser import reset_data

Peter Fogg committed
20 21
TEST_ROOT = settings.COMMON_TEST_DATA_ROOT

22

23
@step('I (?:visit|access|open) the Studio homepage$')
Don Mitchell committed
24
def i_visit_the_studio_homepage(_step):
25 26 27
    # To make this go to port 8001, put
    # LETTUCE_SERVER_PORT = 8001
    # in your settings.py file.
28
    world.visit('/')
29
    signin_css = 'a.action-signin'
30
    assert world.is_css_present(signin_css)
31

32

33
@step('I am logged into Studio$')
Don Mitchell committed
34
def i_am_logged_into_studio(_step):
35 36
    log_into_studio()

37

38
@step('I confirm the alert$')
Don Mitchell committed
39
def i_confirm_with_ok(_step):
40 41
    world.browser.get_alert().accept()

42

43
@step(u'I press the "([^"]*)" delete icon$')
Don Mitchell committed
44
def i_press_the_category_delete_icon(_step, category):
45 46 47
    if category == 'section':
        css = 'a.delete-button.delete-section-button span.delete-icon'
    elif category == 'subsection':
Calen Pennington committed
48
        css = 'a.delete-button.delete-subsection-button  span.delete-icon'
49 50
    else:
        assert False, 'Invalid category: %s' % category
51
    world.css_click(css)
52

53

cahrens committed
54
@step('I have opened a new course in Studio$')
Don Mitchell committed
55
def i_have_opened_a_new_course(_step):
56 57
    open_new_course()

58

59 60
@step('(I select|s?he selects) the new course')
def select_new_course(_step, whom):
61 62
    course_link_css = 'a.course-link'
    world.css_click(course_link_css)
63 64


65
@step(u'I press the "([^"]*)" notification button$')
66
def press_the_notification_button(_step, name):
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
    # TODO: fix up this code. Selenium is not dealing well with css transforms,
    # as it thinks that the notification and the buttons are always visible

    # First wait for the notification to pop up
    notification_css = 'div#page-notification div.wrapper-notification'
    world.wait_for_visible(notification_css)

    # You would think that the above would have worked, but it doesn't.
    # Brute force wait for now.
    world.wait(.5)

    # Now make sure the button is there
    btn_css = 'div#page-notification a.action-%s' % name.lower()
    world.wait_for_visible(btn_css)

    # You would think that the above would have worked, but it doesn't.
    # Brute force wait for now.
    world.wait(.5)

86
    if world.is_firefox():
87 88 89 90
        # This is done to explicitly make the changes save on firefox.
        # It will remove focus from the previously focused element
        world.trigger_event(btn_css, event='focus')
        world.browser.execute_script("$('{}').click()".format(btn_css))
91
    else:
92
        world.css_click(btn_css)
93 94 95


@step('I change the "(.*)" field to "(.*)"$')
96
def i_change_field_to_value(_step, field, value):
97
    field_css = '#%s' % '-'.join([s.lower() for s in field.split()])
98 99 100
    ele = world.css_find(field_css).first
    ele.fill(value)
    ele._element.send_keys(Keys.ENTER)
101 102 103


@step('I reset the database')
104 105 106 107 108 109 110 111 112 113
def reset_the_db(_step):
    """
    When running Lettuce tests using examples (i.e. "Confirmation is
    shown on save" in course-settings.feature), the normal hooks
    aren't called between examples. reset_data should run before each
    scenario to flush the test database. When this doesn't happen we
    get errors due to trying to insert a non-unique entry. So instead,
    we delete the database manually. This has the effect of removing
    any users and courses that have been created during the test run.
    """
114 115
    reset_data(None)

116

117 118 119 120
@step('I see a confirmation that my changes have been saved')
def i_see_a_confirmation(step):
    confirmation_css = '#alert-confirmation'
    assert world.is_css_present(confirmation_css)
121 122


123
def open_new_course():
124
    world.clear_courses()
JonahStanley committed
125
    create_studio_user()
cahrens committed
126 127 128
    log_into_studio()
    create_a_course()

129

130 131
def create_studio_user(
        uname='robot',
132 133
        email='robot+studio@edx.org',
        password='test',
134
        is_staff=False):
135
    studio_user = world.UserFactory(
136
        username=uname,
137 138 139
        email=email,
        password=password,
        is_staff=is_staff)
140

141
    registration = world.RegistrationFactory(user=studio_user)
142 143 144
    registration.register(studio_user)
    registration.activate()

145 146
    return studio_user

147

148
def fill_in_course_info(
149 150
        name='Robot Super Course',
        org='MITx',
151
        num='101',
152
        run='2013_Spring'):
153 154 155
    world.css_fill('.new-course-name', name)
    world.css_fill('.new-course-org', org)
    world.css_fill('.new-course-number', num)
156
    world.css_fill('.new-course-run', run)
157

158

159 160 161
def log_into_studio(
        uname='robot',
        email='robot+studio@edx.org',
162 163
        password='test',
        name='Robot Studio'):
164

165
    world.log_in(username=uname, password=password, email=email, name=name)
166
    # Navigate to the studio dashboard
167
    world.visit('/')
168
    assert_in(uname, world.css_text('h2.title', timeout=10))
169

170

171 172 173 174 175 176 177 178 179 180 181 182
def add_course_author(user, course):
    """
    Add the user to the instructor group of the course
    so they will have the permissions to see it in studio
    """
    for role in ("staff", "instructor"):
        groupname = get_course_groupname_for_role(course.location, role)
        group, __ = Group.objects.get_or_create(name=groupname)
        user.groups.add(group)
    user.save()


183
def create_a_course():
184 185 186 187 188 189
    course = world.CourseFactory.create(org='MITx', course='999', display_name='Robot Super Course')
    world.scenario_dict['COURSE'] = course

    user = world.scenario_dict.get("USER")
    if not user:
        user = get_user_by_email('robot+studio@edx.org')
190

191
    add_course_author(user, course)
192 193 194

    # Navigate to the studio dashboard
    world.visit('/')
JonahStanley committed
195
    course_link_css = 'a.course-link'
196
    world.css_click(course_link_css)
197
    course_title_css = 'span.course-title'
198
    assert_true(world.is_css_present(course_title_css))
199

200

201 202
def add_section(name='My Section'):
    link_css = 'a.new-courseware-section-button'
203
    world.css_click(link_css)
cahrens committed
204 205
    name_css = 'input.new-section-name'
    save_css = 'input.new-section-name-save'
206 207
    world.css_fill(name_css, name)
    world.css_click(save_css)
208
    span_css = 'span.section-name-span'
209
    assert_true(world.is_css_present(span_css))
210

Jay Zoldak committed
211

212 213
def add_subsection(name='Subsection One'):
    css = 'a.new-subsection-item'
214
    world.css_click(css)
215 216
    name_css = 'input.new-subsection-name-input'
    save_css = 'input.new-subsection-name-save'
217 218
    world.css_fill(name_css, name)
    world.css_click(save_css)
cahrens committed
219 220 221


def set_date_and_time(date_css, desired_date, time_css, desired_time):
222
    world.css_fill(date_css, desired_date)
cahrens committed
223
    # hit TAB to get to the time field
224
    e = world.css_find(date_css).first
Don Mitchell committed
225
    # pylint: disable=W0212
cahrens committed
226
    e._element.send_keys(Keys.TAB)
227 228
    world.css_fill(time_css, desired_time)
    e = world.css_find(time_css).first
cahrens committed
229
    e._element.send_keys(Keys.TAB)
230
    time.sleep(float(1))
231 232


233 234 235 236
@step('I have enabled the (.*) advanced module$')
def i_enabled_the_advanced_module(step, module):
    step.given('I have opened a new course section in Studio')
    world.css_click('.nav-course-settings')
237
    world.css_click('.nav-course-settings-advanced a')
238 239 240 241
    type_in_codemirror(0, '["%s"]' % module)
    press_the_notification_button(step, 'Save')


242 243 244 245 246 247
@step('I have clicked the new unit button')
def open_new_unit(step):
    step.given('I have opened a new course section in Studio')
    step.given('I have added a new subsection')
    step.given('I expand the first section')
    world.css_click('a.new-unit-item')
248 249


cahrens committed
250
@step('the save notification button is disabled')
251 252 253
def save_button_disabled(step):
    button_css = '.action-save'
    disabled = 'is-disabled'
254
    assert world.css_has_class(button_css, disabled)
255 256


cahrens committed
257 258 259 260 261 262
@step('the "([^"]*)" button is disabled')
def button_disabled(step, value):
    button_css = 'input[value="%s"]' % value
    assert world.css_has_class(button_css, 'is-disabled')


263 264
@step('I confirm the prompt')
def confirm_the_prompt(step):
265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280

    def click_button(btn_css):
        world.css_click(btn_css)
        return world.css_find(btn_css).visible == False

    prompt_css = 'div.prompt.has-actions'
    world.wait_for_visible(prompt_css)

    btn_css = 'a.button.action-primary'
    world.wait_for_visible(btn_css)

    # Sometimes you can do a click before the prompt is up.
    # Thus we need some retry logic here.
    world.wait_for(lambda _driver: click_button(btn_css))

    assert_false(world.css_find(btn_css).visible)
281 282


283 284 285 286 287
@step(u'I am shown a (.*)$')
def i_am_shown_a_notification(step, notification_type):
    assert world.is_css_present('.wrapper-%s' % notification_type)


288
def type_in_codemirror(index, text):
289
    world.wait(1)  # For now, slow this down so that it works. TODO: fix it.
290
    world.css_click("div.CodeMirror-lines", index=index)
291
    world.browser.execute_script("$('div.CodeMirror.CodeMirror-focused > div').css('overflow', '')")
292 293 294 295 296 297
    g = world.css_find("div.CodeMirror.CodeMirror-focused > div > textarea")
    if world.is_mac():
        g._element.send_keys(Keys.COMMAND + 'a')
    else:
        g._element.send_keys(Keys.CONTROL + 'a')
    g._element.send_keys(Keys.DELETE)
298
    g._element.send_keys(text)
299 300
    if world.is_firefox():
        world.trigger_event('div.CodeMirror', index=index, event='blur')
Peter Fogg committed
301 302 303 304


def upload_file(filename):
    path = os.path.join(TEST_ROOT, filename)
305 306
    world.browser.execute_script("$('input.file-input').css('display', 'block')")
    world.browser.attach_file('file', os.path.abspath(path))
Peter Fogg committed
307 308
    button_css = '.upload-dialog .action-upload'
    world.css_click(button_css)
309 310 311 312 313 314 315 316 317 318 319 320 321


@step(u'"([^"]*)" logs in$')
def other_user_login(step, name):
    step.given('I log out')
    world.visit('/')

    signin_css = 'a.action-signin'
    world.is_css_present(signin_css)
    world.css_click(signin_css)

    def fill_login_form():
        login_form = world.browser.find_by_css('form#login_form')
cahrens committed
322 323
        login_form.find_by_name('email').fill(name + '@edx.org')
        login_form.find_by_name('password').fill("test")
324 325 326
        login_form.find_by_name('submit').click()
    world.retry_on_exception(fill_login_form)
    assert_true(world.is_css_present('.new-course-button'))
cahrens committed
327
    world.scenario_dict['USER'] = get_user_by_email(name + '@edx.org')
328 329 330 331


@step(u'the user "([^"]*)" exists( as a course (admin|staff member|is_staff))?$')
def create_other_user(_step, name, has_extra_perms, role_name):
cahrens committed
332 333
    email = name + '@edx.org'
    user = create_studio_user(uname=name, password="test", email=email)
334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353
    if has_extra_perms:
        if role_name == "is_staff":
            user.is_staff = True
        else:
            if role_name == "admin":
                # admins get staff privileges, as well
                roles = ("staff", "instructor")
            else:
                roles = ("staff",)
            location = world.scenario_dict["COURSE"].location
            for role in roles:
                groupname = get_course_groupname_for_role(location, role)
                group, __ = Group.objects.get_or_create(name=groupname)
                user.groups.add(group)
        user.save()


@step('I log out')
def log_out(_step):
    world.visit('logout')