BLD-1117: Add read-only list of Group Configurations.
This commit is contained in:
@@ -3,6 +3,10 @@ Course Advanced Settings page
|
||||
"""
|
||||
|
||||
from .course_page import CoursePage
|
||||
from .utils import press_the_notification_button, type_in_codemirror, get_codemirror_value
|
||||
|
||||
|
||||
KEY_CSS = '.key h3.title'
|
||||
|
||||
|
||||
class AdvancedSettingsPage(CoursePage):
|
||||
@@ -14,3 +18,27 @@ class AdvancedSettingsPage(CoursePage):
|
||||
|
||||
def is_browser_on_page(self):
|
||||
return self.q(css='body.advanced').present
|
||||
|
||||
def _get_index_of(self, expected_key):
|
||||
for i, element in enumerate(self.q(css=KEY_CSS)):
|
||||
# Sometimes get stale reference if I hold on to the array of elements
|
||||
key = self.q(css=KEY_CSS).nth(i).text[0]
|
||||
if key == expected_key:
|
||||
return i
|
||||
|
||||
return -1
|
||||
|
||||
def save(self):
|
||||
press_the_notification_button(self, "Save")
|
||||
|
||||
def cancel(self):
|
||||
press_the_notification_button(self, "Cancel")
|
||||
|
||||
def set(self, key, new_value):
|
||||
index = self._get_index_of(key)
|
||||
type_in_codemirror(self, index, new_value)
|
||||
self.save()
|
||||
|
||||
def get(self, key):
|
||||
index = self._get_index_of(key)
|
||||
return get_codemirror_value(self, index)
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
"""
|
||||
Course Group Configurations page.
|
||||
"""
|
||||
|
||||
from .course_page import CoursePage
|
||||
|
||||
|
||||
class GroupConfigurationsPage(CoursePage):
|
||||
"""
|
||||
Course Group Configurations page.
|
||||
"""
|
||||
|
||||
url_path = "group_configurations"
|
||||
|
||||
def is_browser_on_page(self):
|
||||
return self.q(css='body.view-group-configurations').present
|
||||
|
||||
def group_configurations(self):
|
||||
"""
|
||||
Returns list of the group configurations for the course.
|
||||
"""
|
||||
css = '.wrapper-group-configuration'
|
||||
return [GroupConfiguration(self, index) for index in xrange(len(self.q(css=css)))]
|
||||
|
||||
|
||||
class GroupConfiguration(object):
|
||||
"""
|
||||
Group Configuration wrapper.
|
||||
"""
|
||||
|
||||
def __init__(self, page, index):
|
||||
self.page = page
|
||||
self.SELECTOR = '.view-group-configuration-{}'.format(index)
|
||||
self.index = index
|
||||
|
||||
def get_selector(self, css=''):
|
||||
return ' '.join([self.SELECTOR, css])
|
||||
|
||||
def find_css(self, selector):
|
||||
"""
|
||||
Find elements as defined by css locator.
|
||||
"""
|
||||
return self.page.q(css=self.get_selector(css=selector))
|
||||
|
||||
def toggle(self):
|
||||
"""
|
||||
Expand/collapse group configuration.
|
||||
"""
|
||||
css = 'a.group-toggle'
|
||||
self.find_css(css).first.click()
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
"""
|
||||
Returns group configuration id.
|
||||
"""
|
||||
css = '.group-configuration-id .group-configuration-value'
|
||||
return self.find_css(css).first.text[0]
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""
|
||||
Returns group configuration name.
|
||||
"""
|
||||
css = '.group-configuration-title'
|
||||
return self.find_css(css).first.text[0]
|
||||
|
||||
@property
|
||||
def description(self):
|
||||
"""
|
||||
Returns group configuration description.
|
||||
"""
|
||||
css = '.group-configuration-description'
|
||||
return self.find_css(css).first.text[0]
|
||||
|
||||
@property
|
||||
def groups(self):
|
||||
"""
|
||||
Returns list of groups.
|
||||
"""
|
||||
css = '.group'
|
||||
|
||||
def group_selector(config_index, group_index):
|
||||
return self.get_selector('.groups-{} .group-{} '.format(config_index, group_index))
|
||||
|
||||
return [Group(self.page, group_selector(self.index, index)) for index, element in enumerate(self.find_css(css))]
|
||||
|
||||
def __repr__(self):
|
||||
return "<{}:{}>".format(self.__class__.__name__, self.name)
|
||||
|
||||
|
||||
class Group(object):
|
||||
"""
|
||||
Group wrapper.
|
||||
"""
|
||||
def __init__(self, page, prefix_selector):
|
||||
self.page = page
|
||||
self.prefix = prefix_selector
|
||||
|
||||
def find_css(self, selector):
|
||||
"""
|
||||
Find elements as defined by css locator.
|
||||
"""
|
||||
return self.page.q(css=self.prefix + selector)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""
|
||||
Returns group name.
|
||||
"""
|
||||
css = '.group-name'
|
||||
return self.find_css(css).first.text[0]
|
||||
|
||||
@property
|
||||
def allocation(self):
|
||||
"""
|
||||
Returns allocation for the group.
|
||||
"""
|
||||
css = '.group-allocation'
|
||||
return self.find_css(css).first.text[0]
|
||||
|
||||
def __repr__(self):
|
||||
return "<{}:{}>".format(self.__class__.__name__, self.name)
|
||||
@@ -37,6 +37,18 @@ def wait_for_notification(page):
|
||||
Promise(_is_saving_done, 'Notification should have been hidden.', timeout=60).fulfill()
|
||||
|
||||
|
||||
def press_the_notification_button(page, name):
|
||||
# Because the notification uses a CSS transition,
|
||||
# Selenium will always report it as being visible.
|
||||
# This makes it very difficult to successfully click
|
||||
# the "Save" button at the UI level.
|
||||
# Instead, we use JavaScript to reliably click
|
||||
# the button.
|
||||
btn_css = 'div#page-notification a.action-%s' % name.lower()
|
||||
page.browser.execute_script("$('{}').focus().click()".format(btn_css))
|
||||
page.wait_for_ajax()
|
||||
|
||||
|
||||
def add_discussion(page, menu_index):
|
||||
"""
|
||||
Add a new instance of the discussion category.
|
||||
@@ -66,3 +78,20 @@ def add_advanced_component(page, menu_index, name):
|
||||
Promise(is_advanced_components_showing, "Advanced component menu not showing").fulfill()
|
||||
|
||||
click_css(page, 'a[data-category={}]'.format(name))
|
||||
|
||||
|
||||
def type_in_codemirror(page, index, text, find_prefix="$"):
|
||||
script = """
|
||||
var cm = {find_prefix}('div.CodeMirror:eq({index})').get(0).CodeMirror;
|
||||
CodeMirror.signal(cm, "focus", cm);
|
||||
cm.setValue(arguments[0]);
|
||||
CodeMirror.signal(cm, "blur", cm);""".format(index=index, find_prefix=find_prefix)
|
||||
page.browser.execute_script(script, str(text))
|
||||
|
||||
|
||||
def get_codemirror_value(page, index=0, find_prefix="$"):
|
||||
return page.browser.execute_script(
|
||||
"""
|
||||
return {find_prefix}('div.CodeMirror:eq({index})').get(0).CodeMirror.getValue();
|
||||
""".format(index=index, find_prefix=find_prefix)
|
||||
)
|
||||
|
||||
@@ -2,16 +2,22 @@
|
||||
Acceptance tests for Studio related to the split_test module.
|
||||
"""
|
||||
|
||||
import json
|
||||
from unittest import skip
|
||||
|
||||
from ..fixtures.course import CourseFixture, XBlockFixtureDesc
|
||||
|
||||
from ..pages.studio.component_editor import ComponentEditorView
|
||||
from ..pages.studio.settings_advanced import AdvancedSettingsPage
|
||||
from ..pages.studio.settings_group_configurations import GroupConfigurationsPage
|
||||
from ..pages.studio.auto_auth import AutoAuthPage
|
||||
from test_studio_container import ContainerBase
|
||||
from ..pages.studio.utils import add_advanced_component
|
||||
from xmodule.partitions.partitions import Group, UserPartition
|
||||
from bok_choy.promise import Promise
|
||||
|
||||
from .helpers import UniqueCourseTest
|
||||
|
||||
|
||||
class SplitTest(ContainerBase):
|
||||
"""
|
||||
@@ -154,3 +160,147 @@ class SplitTest(ContainerBase):
|
||||
container = self.create_poorly_configured_split_instance()
|
||||
container.delete(0)
|
||||
self.verify_groups(container, ['alpha'], [], verify_missing_groups_not_present=False)
|
||||
|
||||
|
||||
class SettingsMenuTest(UniqueCourseTest):
|
||||
"""
|
||||
Tests that Setting menu is rendered correctly in Studio
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(SettingsMenuTest, self).setUp()
|
||||
|
||||
course_fix = CourseFixture(**self.course_info)
|
||||
course_fix.install()
|
||||
|
||||
self.auth_page = AutoAuthPage(
|
||||
self.browser,
|
||||
staff=False,
|
||||
username=course_fix.user.get('username'),
|
||||
email=course_fix.user.get('email'),
|
||||
password=course_fix.user.get('password')
|
||||
)
|
||||
self.auth_page.visit()
|
||||
|
||||
self.advanced_settings = AdvancedSettingsPage(
|
||||
self.browser,
|
||||
self.course_info['org'],
|
||||
self.course_info['number'],
|
||||
self.course_info['run']
|
||||
)
|
||||
self.advanced_settings.visit()
|
||||
|
||||
def test_link_exist_if_split_test_enabled(self):
|
||||
"""
|
||||
Ensure that the link to the "Group Configurations" page is shown in the
|
||||
Settings menu.
|
||||
"""
|
||||
link_css = 'li.nav-course-settings-group-configurations a'
|
||||
self.assertFalse(self.advanced_settings.q(css=link_css).present)
|
||||
|
||||
self.advanced_settings.set('Advanced Module List', '["split_test"]')
|
||||
|
||||
self.browser.refresh()
|
||||
self.advanced_settings.wait_for_page()
|
||||
|
||||
self.assertIn(
|
||||
"split_test",
|
||||
json.loads(self.advanced_settings.get('Advanced Module List')),
|
||||
)
|
||||
|
||||
self.assertTrue(self.advanced_settings.q(css=link_css).present)
|
||||
|
||||
def test_link_does_not_exist_if_split_test_disabled(self):
|
||||
"""
|
||||
Ensure that the link to the "Group Configurations" page does not exist
|
||||
in the Settings menu.
|
||||
"""
|
||||
link_css = 'li.nav-course-settings-group-configurations a'
|
||||
self.advanced_settings.set('Advanced Module List', '[]')
|
||||
self.browser.refresh()
|
||||
self.advanced_settings.wait_for_page()
|
||||
self.assertFalse(self.advanced_settings.q(css=link_css).present)
|
||||
|
||||
|
||||
class GroupConfigurationsTest(UniqueCourseTest):
|
||||
"""
|
||||
Tests that Group Configurations page works correctly with previously
|
||||
added configurations in Studio
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(GroupConfigurationsTest, self).setUp()
|
||||
|
||||
course_fix = CourseFixture(**self.course_info)
|
||||
course_fix.add_advanced_settings({
|
||||
u"advanced_modules": {"value": ["split_test"]},
|
||||
})
|
||||
|
||||
course_fix.install()
|
||||
self.course_fix = course_fix
|
||||
self.user = course_fix.user
|
||||
|
||||
self.auth_page = AutoAuthPage(
|
||||
self.browser,
|
||||
staff=False,
|
||||
username=course_fix.user.get('username'),
|
||||
email=course_fix.user.get('email'),
|
||||
password=course_fix.user.get('password')
|
||||
)
|
||||
self.auth_page.visit()
|
||||
|
||||
self.page = GroupConfigurationsPage(
|
||||
self.browser,
|
||||
self.course_info['org'],
|
||||
self.course_info['number'],
|
||||
self.course_info['run']
|
||||
)
|
||||
|
||||
def test_no_group_configurations_added(self):
|
||||
"""
|
||||
Ensure that message telling me to create a new group configuration is
|
||||
shown when group configurations were not added.
|
||||
"""
|
||||
self.page.visit()
|
||||
css = ".wrapper-content .no-group-configurations-content"
|
||||
self.assertTrue(self.page.q(css=css).present)
|
||||
self.assertIn(
|
||||
"You haven't created any group configurations yet.",
|
||||
self.page.q(css=css).text[0]
|
||||
)
|
||||
|
||||
def test_group_configurations_have_correct_data(self):
|
||||
"""
|
||||
Ensure that the group configuration is rendered correctly in
|
||||
expanded/collapsed mode.
|
||||
"""
|
||||
self.course_fix.add_advanced_settings({
|
||||
u"user_partitions": {
|
||||
"value": [
|
||||
UserPartition(0, 'Name of the Group Configuration', 'Description of the group configuration.', [Group("0", 'Group 0'), Group("1", 'Group 1')]).to_json(),
|
||||
UserPartition(1, 'Name of second Group Configuration', 'Second group configuration.', [Group("0", 'Alpha'), Group("1", 'Beta'), Group("2", 'Gamma')]).to_json()
|
||||
],
|
||||
},
|
||||
})
|
||||
self.course_fix._add_advanced_settings()
|
||||
|
||||
self.page.visit()
|
||||
|
||||
config = self.page.group_configurations()[0]
|
||||
self.assertIn("Name of the Group Configuration", config.name)
|
||||
self.assertEqual(config.id, '0')
|
||||
config.toggle()
|
||||
self.assertIn("Description of the group configuration.", config.description)
|
||||
self.assertEqual(len(config.groups), 2)
|
||||
|
||||
self.assertEqual("Group 0", config.groups[0].name)
|
||||
self.assertEqual("50%", config.groups[0].allocation)
|
||||
|
||||
config = self.page.group_configurations()[1]
|
||||
self.assertIn("Name of second Group Configuration", config.name)
|
||||
self.assertEqual(len(config.groups), 0) # no groups when the partition is collapsed
|
||||
config.toggle()
|
||||
self.assertEqual(len(config.groups), 3)
|
||||
|
||||
self.assertEqual("Beta", config.groups[1].name)
|
||||
self.assertEqual("33%", config.groups[1].allocation)
|
||||
|
||||
Reference in New Issue
Block a user