diff --git a/cms/djangoapps/contentstore/features/advanced-settings.feature b/cms/djangoapps/contentstore/features/advanced-settings.feature index 558294e890..ca5b62e596 100644 --- a/cms/djangoapps/contentstore/features/advanced-settings.feature +++ b/cms/djangoapps/contentstore/features/advanced-settings.feature @@ -11,6 +11,7 @@ Feature: Advanced (manual) course policy Given I am on the Advanced Course Settings page in Studio Then the settings are alphabetized + @skip-phantom Scenario: Test cancel editing key value Given I am on the Advanced Course Settings page in Studio When I edit the value of a policy key @@ -19,6 +20,7 @@ Feature: Advanced (manual) course policy And I reload the page Then the policy key value is unchanged + @skip-phantom Scenario: Test editing key value Given I am on the Advanced Course Settings page in Studio When I edit the value of a policy key and save @@ -26,6 +28,7 @@ Feature: Advanced (manual) course policy And I reload the page Then the policy key value is changed + @skip-phantom Scenario: Test how multi-line input appears Given I am on the Advanced Course Settings page in Studio When I create a JSON object as a value @@ -33,6 +36,7 @@ Feature: Advanced (manual) course policy And I reload the page Then it is displayed as formatted + @skip-phantom Scenario: Test automatic quoting of non-JSON values Given I am on the Advanced Course Settings page in Studio When I create a non-JSON value not in quotes diff --git a/cms/djangoapps/contentstore/features/advanced-settings.py b/cms/djangoapps/contentstore/features/advanced-settings.py index 6fb102faea..ea5b24b21f 100644 --- a/cms/djangoapps/contentstore/features/advanced-settings.py +++ b/cms/djangoapps/contentstore/features/advanced-settings.py @@ -3,10 +3,7 @@ from lettuce import world, step from common import * -import time -from terrain.steps import reload_the_page - -from nose.tools import assert_true, assert_false, assert_equal +from nose.tools import assert_false, assert_equal """ http://selenium.googlecode.com/svn/trunk/docs/api/py/webdriver/selenium.webdriver.common.keys.html @@ -18,8 +15,8 @@ VALUE_CSS = 'textarea.json' DISPLAY_NAME_KEY = "display_name" DISPLAY_NAME_VALUE = '"Robot Super Course"' -############### ACTIONS #################### +############### ACTIONS #################### @step('I select the Advanced Settings$') def i_select_advanced_settings(step): expand_icon_css = 'li.nav-course-settings i.icon-expand' @@ -38,7 +35,7 @@ def i_am_on_advanced_course_settings(step): @step(u'I press the "([^"]*)" notification button$') def press_the_notification_button(step, name): css = 'a.%s-button' % name.lower() - world.css_click_at(css) + world.css_click(css) @step(u'I edit the value of a policy key$') @@ -52,7 +49,7 @@ def edit_the_value_of_a_policy_key(step): @step(u'I edit the value of a policy key and save$') -def edit_the_value_of_a_policy_key(step): +def edit_the_value_of_a_policy_key_and_save(step): change_display_name_value(step, '"foo"') @@ -90,7 +87,7 @@ def it_is_formatted(step): @step('it is displayed as a string') -def it_is_formatted(step): +def it_is_displayed_as_string(step): assert_policy_entries([DISPLAY_NAME_KEY], ['"quote me"']) diff --git a/cms/djangoapps/contentstore/features/checklists.feature b/cms/djangoapps/contentstore/features/checklists.feature index bccb80b8d7..ddf1adf263 100644 --- a/cms/djangoapps/contentstore/features/checklists.feature +++ b/cms/djangoapps/contentstore/features/checklists.feature @@ -10,6 +10,8 @@ Feature: Course checklists Then I can check and uncheck tasks in a checklist And They are correctly selected after I reload the page + @skip-phantom + @skip-firefox Scenario: A task can link to a location within Studio Given I have opened Checklists When I select a link to the course outline @@ -17,8 +19,9 @@ Feature: Course checklists And I press the browser back button Then I am brought back to the course outline in the correct state + @skip-phantom + @skip-firefox Scenario: A task can link to a location outside Studio Given I have opened Checklists When I select a link to help page Then I am brought to the help page in a new window - diff --git a/cms/djangoapps/contentstore/features/checklists.py b/cms/djangoapps/contentstore/features/checklists.py index 489544f424..d433dbbf0d 100644 --- a/cms/djangoapps/contentstore/features/checklists.py +++ b/cms/djangoapps/contentstore/features/checklists.py @@ -89,8 +89,6 @@ def i_am_brought_to_help_page_in_new_window(step): assert_equal('http://help.edge.edx.org/', world.browser.url) - - ############### HELPER METHODS #################### def verifyChecklist2Status(completed, total, percentage): def verify_count(driver): @@ -107,9 +105,11 @@ def verifyChecklist2Status(completed, total, percentage): def toggleTask(checklist, task): - world.css_click('#course-checklist' + str(checklist) +'-task' + str(task)) + world.css_click('#course-checklist' + str(checklist) + '-task' + str(task)) +# TODO: figure out a way to do this in phantom and firefox +# For now we will mark the scenerios that use this method as skipped def clickActionLink(checklist, task, actionText): # toggle checklist item to make sure that the link button is showing toggleTask(checklist, task) @@ -121,4 +121,3 @@ def clickActionLink(checklist, task, actionText): world.wait_for(verify_action_link_text) action_link.click() - diff --git a/cms/djangoapps/contentstore/features/course-settings.feature b/cms/djangoapps/contentstore/features/course-settings.feature index e869bfe47a..fc9641cb46 100644 --- a/cms/djangoapps/contentstore/features/course-settings.feature +++ b/cms/djangoapps/contentstore/features/course-settings.feature @@ -1,17 +1,20 @@ Feature: Course Settings As a course author, I want to be able to configure my course settings. + @skip-phantom Scenario: User can set course dates Given I have opened a new course in Studio When I select Schedule and Details And I set course dates Then I see the set dates on refresh + @skip-phantom Scenario: User can clear previously set course dates (except start date) Given I have set course dates And I clear all the dates except start Then I see cleared dates on refresh + @skip-phantom Scenario: User cannot clear the course start date Given I have set course dates And I clear the course start date diff --git a/cms/djangoapps/contentstore/features/section.feature b/cms/djangoapps/contentstore/features/section.feature index 08d38367bc..24cbeb3db9 100644 --- a/cms/djangoapps/contentstore/features/section.feature +++ b/cms/djangoapps/contentstore/features/section.feature @@ -3,6 +3,7 @@ Feature: Create Section As a course author I want to create and edit sections + @skip-phantom Scenario: Add a new section to a course Given I have opened a new course in Studio When I click the New Section link diff --git a/cms/djangoapps/contentstore/features/studio-overview-togglesection.feature b/cms/djangoapps/contentstore/features/studio-overview-togglesection.feature index 762dea6838..a0e0a48f9e 100644 --- a/cms/djangoapps/contentstore/features/studio-overview-togglesection.feature +++ b/cms/djangoapps/contentstore/features/studio-overview-togglesection.feature @@ -1,32 +1,33 @@ Feature: Overview Toggle Section In order to quickly view the details of a course's section or to scan the inventory of sections - As a course author - I want to toggle the visibility of each section's subsection details in the overview listing + As a course author + I want to toggle the visibility of each section's subsection details in the overview listing Scenario: The default layout for the overview page is to show sections in expanded view Given I have a course with multiple sections - When I navigate to the course overview page - Then I see the "Collapse All Sections" link - And all sections are expanded + When I navigate to the course overview page + Then I see the "Collapse All Sections" link + And all sections are expanded Scenario: Expand /collapse for a course with no sections Given I have a course with no sections - When I navigate to the course overview page - Then I do not see the "Collapse All Sections" link + When I navigate to the course overview page + Then I do not see the "Collapse All Sections" link + @skip-phantom Scenario: Collapse link appears after creating first section of a course Given I have a course with no sections - When I navigate to the course overview page - And I add a section - Then I see the "Collapse All Sections" link - And all sections are expanded + When I navigate to the course overview page + And I add a section + Then I see the "Collapse All Sections" link + And all sections are expanded @skip-phantom Scenario: Collapse link is not removed after last section of a course is deleted Given I have a course with 1 section - And I navigate to the course overview page - When I press the "section" delete icon - And I confirm the alert + And I navigate to the course overview page + When I press the "section" delete icon + And I confirm the alert Then I see the "Collapse All Sections" link Scenario: Collapsing all sections when all sections are expanded @@ -57,4 +58,4 @@ Feature: Overview Toggle Section When I expand the first section And I click the "Expand All Sections" link Then I see the "Collapse All Sections" link - And all sections are expanded \ No newline at end of file + And all sections are expanded diff --git a/cms/djangoapps/contentstore/features/subsection.feature b/cms/djangoapps/contentstore/features/subsection.feature index cc3b2b1cbb..28285bf8a1 100644 --- a/cms/djangoapps/contentstore/features/subsection.feature +++ b/cms/djangoapps/contentstore/features/subsection.feature @@ -3,13 +3,15 @@ Feature: Create Subsection As a course author I want to create and edit subsections - Scenario: Add a new subsection to a section + @skip-phantom + Scenario: Add a new subsection to a section Given I have opened a new course section in Studio When I click the New Subsection link And I enter the subsection name and click save Then I see my subsection on the Courseware page - Scenario: Add a new subsection (with a name containing a quote) to a section (bug #216) + @skip-phantom + Scenario: Add a new subsection (with a name containing a quote) to a section (bug #216) Given I have opened a new course section in Studio When I click the New Subsection link And I enter a subsection name with a quote and click save @@ -17,7 +19,7 @@ Feature: Create Subsection And I click to edit the subsection name Then I see the complete subsection name with a quote in the editor - Scenario: Assign grading type to a subsection and verify it is still shown after refresh (bug #258) + Scenario: Assign grading type to a subsection and verify it is still shown after refresh (bug #258) Given I have opened a new course section in Studio And I have added a new subsection And I mark it as Homework @@ -25,20 +27,19 @@ Feature: Create Subsection And I reload the page Then I see it marked as Homework - Scenario: Set a due date in a different year (bug #256) + @skip-phantom + Scenario: Set a due date in a different year (bug #256) Given I have opened a new subsection in Studio And I have set a release date and due date in different years Then I see the correct dates And I reload the page Then I see the correct dates - @skip-phantom - Scenario: Delete a subsection + @skip-phantom + Scenario: Delete a subsection Given I have opened a new course section in Studio And I have added a new subsection And I see my subsection on the Courseware page When I press the "subsection" delete icon And I confirm the alert Then the subsection does not exist - - diff --git a/cms/djangoapps/contentstore/tests/test_contentstore.py b/cms/djangoapps/contentstore/tests/test_contentstore.py index e40d7c57b9..ed95d81d67 100644 --- a/cms/djangoapps/contentstore/tests/test_contentstore.py +++ b/cms/djangoapps/contentstore/tests/test_contentstore.py @@ -9,7 +9,6 @@ from tempdir import mkdtemp_clean from fs.osfs import OSFS import copy from json import loads -import traceback from datetime import timedelta from django.contrib.auth.models import User @@ -397,7 +396,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): # We had a bug where orphaned draft nodes caused export to fail. This is here to cover that case. draft_store.clone_item(vertical.location, Location(['i4x', 'edX', 'full', - 'vertical', 'no_references', 'draft'])) + 'vertical', 'no_references', 'draft'])) for child in vertical.get_children(): draft_store.clone_item(child.location, child.location) @@ -478,6 +477,12 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): for child in vertical.get_children(): self.assertTrue(getattr(child, 'is_draft', False)) + # make sure that we don't have a sequential that is in draft mode + sequential = draft_store.get_item(Location(['i4x', 'edX', 'full', + 'sequential', 'Administrivia_and_Circuit_Elements', None])) + + self.assertFalse(getattr(sequential, 'is_draft', False)) + # verify that we have the private vertical test_private_vertical = draft_store.get_item(Location(['i4x', 'edX', 'full', 'vertical', 'vertical_66', None])) diff --git a/cms/envs/acceptance.py b/cms/envs/acceptance.py index 26a8adc92c..1e7a32dc68 100644 --- a/cms/envs/acceptance.py +++ b/cms/envs/acceptance.py @@ -36,3 +36,4 @@ DATABASES = { INSTALLED_APPS += ('lettuce.django',) LETTUCE_APPS = ('contentstore',) LETTUCE_SERVER_PORT = 8001 +LETTUCE_BROWSER = 'chrome' diff --git a/cms/templates/settings.html b/cms/templates/settings.html index cc5dafc57b..3923c0f905 100644 --- a/cms/templates/settings.html +++ b/cms/templates/settings.html @@ -87,12 +87,12 @@ from contentstore import utils

Course Summary Page (for student enrollment and access)

-

${utils.get_lms_link_for_about_page(course_location)}

+

https:${utils.get_lms_link_for_about_page(course_location)}

diff --git a/cms/templates/settings_graders.html b/cms/templates/settings_graders.html index 86be66c950..2e98409585 100644 --- a/cms/templates/settings_graders.html +++ b/cms/templates/settings_graders.html @@ -4,20 +4,20 @@ <%namespace name='static' file='static_content.html'/> <%! -from contentstore import utils +from contentstore import utils %> <%block name="jsextra"> - + - + @@ -97,7 +97,7 @@ from contentstore import utils
  1. - + Leeway on due dates
@@ -112,13 +112,13 @@ from contentstore import utils
    - -
+ +
New Assignment Type - +
diff --git a/common/djangoapps/student/tests/factories.py b/common/djangoapps/student/tests/factories.py index adb51954e8..0d9621fc01 100644 --- a/common/djangoapps/student/tests/factories.py +++ b/common/djangoapps/student/tests/factories.py @@ -2,17 +2,17 @@ from student.models import (User, UserProfile, Registration, CourseEnrollmentAllowed, CourseEnrollment) from django.contrib.auth.models import Group from datetime import datetime -from factory import Factory, SubFactory, post_generation +from factory import DjangoModelFactory, Factory, SubFactory, PostGenerationMethodCall from uuid import uuid4 -class GroupFactory(Factory): +class GroupFactory(DjangoModelFactory): FACTORY_FOR = Group name = 'staff_MITx/999/Robot_Super_Course' -class UserProfileFactory(Factory): +class UserProfileFactory(DjangoModelFactory): FACTORY_FOR = UserProfile user = None @@ -23,19 +23,20 @@ class UserProfileFactory(Factory): goals = 'World domination' -class RegistrationFactory(Factory): +class RegistrationFactory(DjangoModelFactory): FACTORY_FOR = Registration user = None activation_key = uuid4().hex -class UserFactory(Factory): +class UserFactory(DjangoModelFactory): FACTORY_FOR = User username = 'robot' email = 'robot+test@edx.org' - password = 'test' + password = PostGenerationMethodCall('set_password', + 'test') first_name = 'Robot' last_name = 'Test' is_staff = False @@ -44,26 +45,19 @@ class UserFactory(Factory): last_login = datetime(2012, 1, 1) date_joined = datetime(2011, 1, 1) - @post_generation - def set_password(self, create, extracted, **kwargs): - self._raw_password = self.password - self.set_password(self.password) - if create: - self.save() - class AdminFactory(UserFactory): is_staff = True -class CourseEnrollmentFactory(Factory): +class CourseEnrollmentFactory(DjangoModelFactory): FACTORY_FOR = CourseEnrollment user = SubFactory(UserFactory) course_id = 'edX/toy/2012_Fall' -class CourseEnrollmentAllowedFactory(Factory): +class CourseEnrollmentAllowedFactory(DjangoModelFactory): FACTORY_FOR = CourseEnrollmentAllowed email = 'test@edx.org' diff --git a/common/djangoapps/terrain/browser.py b/common/djangoapps/terrain/browser.py index c8cc0c9e4b..1d371a3242 100644 --- a/common/djangoapps/terrain/browser.py +++ b/common/djangoapps/terrain/browser.py @@ -1,6 +1,8 @@ from lettuce import before, after, world from splinter.browser import Browser from logging import getLogger +from django.core.management import call_command +from django.conf import settings # Let the LMS and CMS do their one-time setup # For example, setting up mongo caches @@ -10,18 +12,14 @@ from cms import one_time_startup logger = getLogger(__name__) logger.info("Loading the lettuce acceptance testing terrain file...") -from django.core.management import call_command - @before.harvest def initial_setup(server): ''' Launch the browser once before executing the tests ''' - # Launch the browser app (choose one of these below) - world.browser = Browser('chrome') - # world.browser = Browser('phantomjs') - # world.browser = Browser('firefox') + browser_driver = getattr(settings, 'LETTUCE_BROWSER', 'chrome') + world.browser = Browser(browser_driver) @before.each_scenario @@ -34,6 +32,15 @@ def reset_data(scenario): call_command('flush', interactive=False) +@after.each_scenario +def screenshot_on_error(scenario): + ''' + Save a screenshot to help with debugging + ''' + if scenario.failed: + world.browser.driver.save_screenshot('/tmp/last_failed_scenario.png') + + @after.all def teardown_browser(total): ''' diff --git a/common/djangoapps/terrain/steps.py b/common/djangoapps/terrain/steps.py index a2db80712f..fdab514177 100644 --- a/common/djangoapps/terrain/steps.py +++ b/common/djangoapps/terrain/steps.py @@ -132,6 +132,8 @@ def i_am_logged_in(step): world.create_user('robot') world.log_in('robot', 'test') world.browser.visit(django_url('/')) + # You should not see the login link + assert_equals(world.browser.find_by_css('a#login'), []) @step(u'I am an edX user$') diff --git a/common/djangoapps/xmodule_modifiers.py b/common/djangoapps/xmodule_modifiers.py index 3f6db354d6..45691cd854 100644 --- a/common/djangoapps/xmodule_modifiers.py +++ b/common/djangoapps/xmodule_modifiers.py @@ -105,8 +105,12 @@ def add_histogram(get_html, module, user): return get_html() module_id = module.id - histogram = grade_histogram(module_id) - render_histogram = len(histogram) > 0 + if module.descriptor.has_score: + histogram = grade_histogram(module_id) + render_histogram = len(histogram) > 0 + else: + histogram = None + render_histogram = False if settings.MITX_FEATURES.get('ENABLE_LMS_MIGRATION'): [filepath, filename] = getattr(module.descriptor, 'xml_attributes', {}).get('filename', ['', None]) diff --git a/common/lib/capa/capa/inputtypes.py b/common/lib/capa/capa/inputtypes.py index 5a89b69136..e253b61948 100644 --- a/common/lib/capa/capa/inputtypes.py +++ b/common/lib/capa/capa/inputtypes.py @@ -668,6 +668,8 @@ class MatlabInput(CodeInput): # Check if problem has been queued self.queuename = 'matlab' self.queue_msg = '' + # this is only set if we don't have a graded response + # the graded response takes precedence if 'queue_msg' in self.input_state and self.status in ['queued', 'incomplete', 'unsubmitted']: self.queue_msg = self.input_state['queue_msg'] if 'queuestate' in self.input_state and self.input_state['queuestate'] == 'queued': @@ -712,11 +714,23 @@ class MatlabInput(CodeInput): self.input_state['queuestate'] = None self.input_state['queuekey'] = None + def button_enabled(self): + """ Return whether or not we want the 'Test Code' button visible + + Right now, we only want this button to show up when a problem has not been + checked. + """ + if self.status in ['correct', 'incorrect']: + return False + else: + return True + def _extra_context(self): ''' Set up additional context variables''' extra_context = { 'queue_len': str(self.queue_len), - 'queue_msg': self.queue_msg + 'queue_msg': self.queue_msg, + 'button_enabled': self.button_enabled(), } return extra_context @@ -766,10 +780,6 @@ class MatlabInput(CodeInput): lms_key=queuekey, queue_name=self.queuename) - # save the input state - self.input_state['queuekey'] = queuekey - self.input_state['queuestate'] = 'queued' - # construct xqueue body student_info = {'anonymous_student_id': anonymous_student_id, 'submission_time': qtime} @@ -779,6 +789,10 @@ class MatlabInput(CodeInput): (error, msg) = qinterface.send_to_queue(header=xheader, body=json.dumps(contents)) + # save the input state if successful + if error == 0: + self.input_state['queuekey'] = queuekey + self.input_state['queuestate'] = 'queued' return {'success': error == 0, 'message': msg} diff --git a/common/lib/capa/capa/templates/matlabinput.html b/common/lib/capa/capa/templates/matlabinput.html index 6c02e8e68e..69e412f43e 100644 --- a/common/lib/capa/capa/templates/matlabinput.html +++ b/common/lib/capa/capa/templates/matlabinput.html @@ -33,9 +33,11 @@ ${queue_msg|n} + % if button_enabled:
- +
+ %endif + % endif +% endif diff --git a/lms/templates/discussion/_filter_dropdown.html b/lms/templates/discussion/_filter_dropdown.html index fef4abb11f..dd5b94f910 100644 --- a/lms/templates/discussion/_filter_dropdown.html +++ b/lms/templates/discussion/_filter_dropdown.html @@ -33,6 +33,14 @@ Show All Discussions + %if flag_moderator: +
  • + + Show Flagged Discussions + +
  • + + %endif
  • Following diff --git a/lms/templates/discussion/_underscore_templates.html b/lms/templates/discussion/_underscore_templates.html index 24e3b467be..110e6ffc19 100644 --- a/lms/templates/discussion/_underscore_templates.html +++ b/lms/templates/discussion/_underscore_templates.html @@ -3,6 +3,7 @@