Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 48 additions & 12 deletions cms/djangoapps/contentstore/features/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# pylint: disable=W0621

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

from auth.authz import get_user_by_email, get_course_groupname_for_role
from django.conf import settings
Expand Down Expand Up @@ -224,14 +224,50 @@ def i_enabled_the_advanced_module(step, module):
press_the_notification_button(step, 'Save')


@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')
old_url = world.browser.url
world.css_click('a.new-unit-item')
world.wait_for(lambda x: world.browser.url != old_url)
@world.absorb
def create_course_with_unit():
"""
Prepare for tests by creating a course with a section, subsection, and unit.
Performs the following:
Clear out all courseware
Create a course with a section, subsection, and unit
Create a user and make that user a course author
Log the user into studio
Open the course from the dashboard
Expand the section and click on the New Unit link
The end result is the page where the user is editing the new unit
"""
world.clear_courses()
course = world.CourseFactory.create()
world.scenario_dict['COURSE'] = course
section = world.ItemFactory.create(parent_location=course.location)
world.ItemFactory.create(
parent_location=section.location,
category='sequential',
display_name='Subsection One',
)
user = create_studio_user(is_staff=False)
add_course_author(user, course)

log_into_studio()
world.css_click('a.course-link')

css_selectors = [
'div.section-item a.expand-collapse-icon', 'a.new-unit-item'
]
for selector in css_selectors:
world.css_click(selector)

world.wait_for_mathjax()
world.wait_for_xmodule()

assert world.is_css_present('ul.new-component-type')


@step('I have clicked the new unit button$')
@step(u'I am in Studio editing a new unit$')
def edit_new_unit(step):
create_course_with_unit()


@step('the save notification button is disabled')
Expand Down Expand Up @@ -267,9 +303,9 @@ def click_button(btn_css):
assert_false(world.css_find(btn_css).visible)


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


def type_in_codemirror(index, text):
Expand Down
6 changes: 0 additions & 6 deletions cms/djangoapps/contentstore/features/component.feature
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,3 @@ Feature: CMS.Component Adding
And I add a "Blank Advanced Problem" "Advanced Problem" component
And I delete all components
Then I see no components

Scenario: I see a notification on save
Given I am in Studio editing a new unit
And I add a "Discussion" "single step" component
And I edit and save a component
Then I am shown a notification
87 changes: 11 additions & 76 deletions cms/djangoapps/contentstore/features/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,52 +2,19 @@
#pylint: disable=W0621

from lettuce import world, step
from nose.tools import assert_true, assert_in, assert_equal # pylint: disable=E0611
from common import create_studio_user, add_course_author, log_into_studio


@step(u'I am in Studio editing a new unit$')
def add_unit(step):
world.clear_courses()
course = world.CourseFactory.create()
section = world.ItemFactory.create(parent_location=course.location)
world.ItemFactory.create(
parent_location=section.location,
category='sequential',
display_name='Subsection One',)
user = create_studio_user(is_staff=False)
add_course_author(user, course)
log_into_studio()
world.wait_for_requirejs([
"jquery", "gettext", "js/models/course", "coffee/src/models/module",
"coffee/src/views/unit", "jquery.ui",
])
world.wait_for_mathjax()
css_selectors = [
'a.course-link', 'div.section-item a.expand-collapse-icon',
'a.new-unit-item',
]
for selector in css_selectors:
world.css_click(selector)
from nose.tools import assert_true, assert_in # pylint: disable=E0611


@step(u'I add this type of single step component:$')
def add_a_single_step_component(step):
world.wait_for_xmodule()
for step_hash in step.hashes:
component = step_hash['Component']
assert_in(component, ['Discussion', 'Video'])
css_selector = 'a[data-type="{}"]'.format(component.lower())
world.css_click(css_selector)

# In the current implementation, all the "new component"
# buttons are handled by one BackBone.js view.
# If we click two buttons at super-human speed,
# the view will miss the second click while it's
# processing the first.
# To account for this, we wait for each component
# to be created before clicking the next component.
world.wait_for_visible('section.xmodule_{}Module'.format(component))
world.create_component_instance(
step=step,
category='{}'.format(component.lower()),
)


@step(u'I see this type of single step component:$')
Expand All @@ -62,45 +29,13 @@ def see_a_single_step_component(step):

@step(u'I add this type of( Advanced)? (HTML|Problem) component:$')
def add_a_multi_step_component(step, is_advanced, category):
def click_advanced():
css = 'ul.problem-type-tabs a[href="#tab2"]'
world.css_click(css)
my_css = 'ul.problem-type-tabs li.ui-state-active a[href="#tab2"]'
assert(world.css_find(my_css))

def find_matching_link():
"""
Find the link with the specified text. There should be one and only one.
"""
# The tab shows links for the given category
links = world.css_find('div.new-component-{} a'.format(category))

# Find the link whose text matches what you're looking for
matched_links = [link for link in links if link.text == step_hash['Component']]

# There should be one and only one
assert_equal(len(matched_links), 1)
return matched_links[0]

def click_link():
link.click()

world.wait_for_xmodule()
category = category.lower()
for step_hash in step.hashes:
css_selector = 'a[data-type="{}"]'.format(category)
world.css_click(css_selector)
world.wait_for_invisible(css_selector)

if is_advanced:
# Sometimes this click does not work if you go too fast.
world.retry_on_exception(click_advanced, max_attempts=5, ignored_exceptions=AssertionError)

# Retry this in case the list is empty because you tried too fast.
link = world.retry_on_exception(func=find_matching_link, ignored_exceptions=AssertionError)

# Wait for the link to be clickable. If you go too fast it is not.
world.retry_on_exception(click_link)
world.create_component_instance(
step=step,
category='{}'.format(category.lower()),
component_type=step_hash['Component'],
is_advanced=bool(is_advanced),
)


@step(u'I see (HTML|Problem) components in this order:')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,60 +2,89 @@
#pylint: disable=C0111

from lettuce import world
from nose.tools import assert_equal, assert_true # pylint: disable=E0611
from nose.tools import assert_equal, assert_true, assert_in # pylint: disable=E0611
from terrain.steps import reload_the_page


@world.absorb
def create_component_instance(step, component_button_css, category,
expected_css, boilerplate=None,
has_multiple_templates=True):

click_new_component_button(step, component_button_css)

if category in ('problem', 'html'):
def create_component_instance(step, category, component_type=None, is_advanced=False):
"""
Create a new component in a Unit.

def animation_done(_driver):
script = "$('div.new-component').css('display')"
return world.browser.evaluate_script(script) == 'none'
Parameters
----------
category: component type (discussion, html, problem, video)
component_type: for components with multiple templates, the link text in the menu
is_advanced: for html and problem, is the desired component under the
advanced menu
"""
assert_in(category, ['problem', 'html', 'video', 'discussion'])

world.wait_for(animation_done)
component_button_css = '.large-{}-icon'.format(category.lower())
world.css_click(component_button_css)

if has_multiple_templates:
click_component_from_menu(category, boilerplate, expected_css)
if category in ('problem', 'html'):
world.wait_for_invisible(component_button_css)
click_component_from_menu(category, component_type, is_advanced)

if category in ('video',):
world.wait_for_xmodule()
if category == 'problem':
expected_css = 'section.xmodule_CapaModule'
else:
expected_css = 'section.xmodule_{}Module'.format(category.title())

assert_true(world.is_css_present(expected_css))


@world.absorb
def click_new_component_button(step, component_button_css):
step.given('I have clicked the new unit button')
world.wait_for_requirejs(
["jquery", "js/models/course", "coffee/src/models/module",
"coffee/src/views/unit", "jquery.ui", "domReady!"]
)
world.css_click(component_button_css)


@world.absorb
def click_component_from_menu(category, boilerplate, expected_css):
def _click_advanced():
css = 'ul.problem-type-tabs a[href="#tab2"]'
world.css_click(css)
my_css = 'ul.problem-type-tabs li.ui-state-active a[href="#tab2"]'
assert(world.css_find(my_css))


def _find_matching_link(category, component_type):
"""
Creates a component from `instance_id`. For components with more
than one template, clicks on `elem_css` to create the new
component. Components with only one template are created as soon
as the user clicks the appropriate button, so we assert that the
expected component is present.
Find the link with the specified text. There should be one and only one.
"""
if boilerplate:
elem_css = "a[data-category='{}'][data-boilerplate='{}']".format(category, boilerplate)
else:
elem_css = "a[data-category='{}']:not([data-boilerplate])".format(category)
elements = world.css_find(elem_css)
assert_equal(len(elements), 1)
world.css_click(elem_css)

# The tab shows links for the given category
links = world.css_find('div.new-component-{} a'.format(category))

# Find the link whose text matches what you're looking for
matched_links = [link for link in links if link.text == component_type]

# There should be one and only one
assert_equal(len(matched_links), 1)
return matched_links[0]


def click_component_from_menu(category, component_type, is_advanced):
"""
Creates a component for a category with more
than one template, i.e. HTML and Problem.
For some problem types, it is necessary to click to
the Advanced tab.
The component_type is the link text, e.g. "Blank Common Problem"
"""
if is_advanced:
# Sometimes this click does not work if you go too fast.
world.retry_on_exception(_click_advanced,
ignored_exceptions=AssertionError)

# Retry this in case the list is empty because you tried too fast.
link = world.retry_on_exception(
lambda: _find_matching_link(category, component_type),
ignored_exceptions=AssertionError
)

# Wait for the link to be clickable. If you go too fast it is not.
world.retry_on_exception(lambda: link.click())


@world.absorb
Expand Down
17 changes: 0 additions & 17 deletions cms/djangoapps/contentstore/features/course-overview.feature
Original file line number Diff line number Diff line change
Expand Up @@ -58,20 +58,3 @@ Feature: CMS.Course Overview
And I click the "Expand All Sections" link
Then I see the "Collapse All Sections" link
And all sections are expanded

Scenario: Notification is shown on grading status changes
Given I have a course with 1 section
When I navigate to the course overview page
And I change an assignment's grading status
Then I am shown a notification

# Notification is not shown on reorder for IE
# Safari does not have moveMouseTo implemented
@skip_internetexplorer
@skip_safari
Scenario: Notification is shown on subsection reorder
Given I have opened a new course section in Studio
And I have added a new subsection
And I have added a new subsection
When I reorder subsections
Then I am shown a notification
8 changes: 4 additions & 4 deletions cms/djangoapps/contentstore/features/course-team.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ def other_delete_self(_step):

@step(u'I make "([^"]*)" a course team admin')
def make_course_team_admin(_step, name):
admin_btn_css = '.user-item[data-email="{email}"] .user-actions .add-admin-role'.format(
email=name+'@edx.org')
admin_btn_css = '.user-item[data-email="{name}@edx.org"] .user-actions .add-admin-role'.format(
name=name)
world.css_click(admin_btn_css)


Expand Down Expand Up @@ -80,8 +80,8 @@ def see_course(_step, do_not_see, gender='self'):

@step(u'"([^"]*)" should( not)? be marked as an admin')
def marked_as_admin(_step, name, not_marked_admin):
flag_css = '.user-item[data-email="{email}"] .flag-role.flag-role-admin'.format(
email=name+'@edx.org')
flag_css = '.user-item[data-email="{name}@edx.org"] .flag-role.flag-role-admin'.format(
name=name)
if not_marked_admin:
assert world.is_css_not_present(flag_css)
else:
Expand Down
1 change: 1 addition & 0 deletions cms/djangoapps/contentstore/features/course_import.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from lettuce import world
from django.conf import settings


def import_file(filename):
world.browser.execute_script("$('input.file-input').css('display', 'block')")
path = os.path.join(settings.COMMON_TEST_DATA_ROOT, "imports", filename)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Feature: CMS.Discussion Component Editor
As a course author, I want to be able to create discussion components.

Scenario: User can view metadata
Scenario: User can view discussion component metadata
Given I have created a Discussion Tag
And I edit and select Settings
Then I see three alphabetized settings and their expected values
Expand All @@ -14,7 +14,3 @@ Feature: CMS.Discussion Component Editor
And I edit and select Settings
Then I can modify the display name
And my display name change is persisted on save

Scenario: Creating a discussion takes a single click
Given I have clicked the new unit button
Then creating a discussion takes a single click
Loading