Some content
Some plain text
") - - def test_markdown_rendering(self): - """When I type Markdown into the editor, it should be rendered as formatted Markdown in the preview box""" - self.page.set_new_post_editor_value( - "Some markdown\n" - "\n" - "- line 1\n" - "- line 2" - ) - - self.assertEqual(self.page.get_new_post_preview_value(), ( - "Some markdown
\n" - "\n" - "- \n"
- "
- line 1 \n" - "
- line 2 \n" - "
another name', u'
-
-
What is the sum of $oneseven and 3?
- -What is height of eiffel tower without the antenna?.
-
- Given the data in Table 7
question text
-question text
-Check mathjax has rendered [mathjax]E=mc^2[/mathjax]
-The answer is 1. Partial credit for -1.
-You can use this template as a guide to the OLX markup to use for math expression problems. Edit this component to replace the example with your own assessment.
- -You can use this template as a guide to the OLX markup to use for math expression problems. Edit this component to replace the example with your own assessment.
- -Choose Yes.
-Copied from LMS HTML component
-
-
-
- --
VOICE COMPARISON
-You can access the experimental Voice Comparison tool at the link below.
-
-
Body of HTML Unit.
' - self.courseware = CoursewarePage(self.browser, self.course_id) - past_start_date = datetime.datetime(1974, 6, 22) - self.past_start_date_text = "Jun 22, 1974 at 00:00 UTC" - future_start_date = datetime.datetime(2100, 9, 13) - - course_fixture.add_children( - XBlockFixtureDesc('chapter', 'Test Section').add_children( - XBlockFixtureDesc('sequential', 'Test Subsection').add_children( - XBlockFixtureDesc('vertical', 'Test Unit').add_children( - XBlockFixtureDesc('html', 'Test html', data=self.html_content) - ) - ) - ), - XBlockFixtureDesc( - 'chapter', - 'Unlocked Section', - metadata={'start': past_start_date.isoformat()} - ).add_children( - XBlockFixtureDesc('sequential', 'Unlocked Subsection').add_children( - XBlockFixtureDesc('vertical', 'Unlocked Unit').add_children( - XBlockFixtureDesc('problem', 'pages
" in the code editor and - press OK - And I save the page - Then the page text contains: - "" -pages
- - "" - """ - content = u'pages
' - - # Add HTML Text type component - self._add_component('Text') - self.container_page.edit() - self._add_content(content) - html = self.container_page.content_html - self.assertIn(content, html) - - def test_tinymce_and_codemirror_preserve_span_tags(self): - """ - Scenario: TinyMCE and CodeMirror preserve span tags - Given I have created a Blank HTML Page - When I edit the page - And type "Test" in the code editor and press OK - And I save the page - Then the page text contains: - "" - Test - "" - """ - content = "Test" - - # Add HTML Text type component - self._add_component('Text') - self.container_page.edit() - self._add_content(content) - html = self.container_page.content_html - self.assertIn(content, html) - - def test_tinymce_and_codemirror_preserve_math_tags(self): - """ - Scenario: TinyMCE and CodeMirror preserve math tags - Given I have created a Blank HTML Page - When I edit the page - And type "" in the code editor and press OK - And I save the page - Then the page text contains: - "" - - "" - """ - content = "" - - # Add HTML Text type component - self._add_component('Text') - self.container_page.edit() - self._add_content(content) - html = self.container_page.content_html - self.assertIn(content, html) - - def test_code_format_toolbar_wraps_text_with_code_tags(self): - """ - Scenario: Code format toolbar button wraps text with code tags - Given I have created a Blank HTML Page - When I edit the page - And I set the text to "display as code" and I select the text - And I save the page - Then the page text contains: - "" -display as code
display as code
- " into the Raw Editor
- And I save the page
- Then the page text contains:
- ""
-
- zzzz
-
- ""
- And I edit the page
- Then the Raw Editor contains exactly:
- ""
-
- zzzz
-
- ""
- """
- content = "
- zzzz " - - # Add Raw HTML type component - self._add_component('Raw HTML') - self.container_page.edit() - - # Set content in tinymce editor - type_in_codemirror(self.html_editor, 0, content) - self.html_editor.save_content() - - # The HTML of the content added through tinymce editor - html = self.container_page.content_html - # The text content should be present with its tag preserved - self.assertIn(content, html) - - self.container_page.edit() - editor_value = self.html_editor.editor_value - # The tinymce editor value should not be different from the content added in the start - self.assertEqual(content, editor_value) - - def test_tinymce_toolbar_buttons_are_as_expected(self): - """ - Scenario: TinyMCE toolbar buttons are as expected - Given I have created a Blank HTML Page - When I edit the page - Then the expected toolbar buttons are displayed - """ - # Add HTML Text type component - self._add_component('Text') - self.container_page.edit() - - expected_buttons = [ - u'bold', - u'italic', - u'underline', - u'forecolor', - # This is our custom "code style" button, which uses an image instead of a class. - u'none', - u'alignleft', - u'aligncenter', - u'alignright', - u'alignjustify', - u'bullist', - u'numlist', - u'outdent', - u'indent', - u'blockquote', - u'link', - u'unlink', - u'image' - ] - toolbar_dropdowns = self.html_editor.toolbar_dropdown_titles - # The toolbar is divided in two sections: drop-downs and all other formatting buttons - # The assertions under asserts for the drop-downs - self.assertEqual(len(toolbar_dropdowns), 2) - self.assertEqual(['Paragraph', 'Font Family'], toolbar_dropdowns) - - toolbar_buttons = self.html_editor.toolbar_button_titles - # The assertions under asserts for all the remaining formatting buttons - self.assertEqual(len(toolbar_buttons), len(expected_buttons)) - - for index, button in enumerate(expected_buttons): - class_name = toolbar_buttons[index] - self.assertEqual("mce-ico mce-i-" + button, class_name) - - def test_static_links_converted(self): - """ - Scenario: Static links are converted when switching between code editor and WYSIWYG views - Given I have created a Blank HTML Page - When I edit the page - And type "
" in the code editor and press OK
- Then the src link is rewritten to the asset link /asset-v1:(course_id)+type@asset+block/image.jpg
- And the code editor displays "
" - """ - value = '
'
-
- # Add HTML Text type component
- self._add_component('Text')
- self.container_page.edit()
- self.html_editor.set_raw_content(value)
- self.html_editor.save_content()
- html = self.container_page.content_html
- src = "/asset-v1:{}+type@asset+block/image.jpg".format(self.course_id.strip('course-v1:'))
- self.assertIn(src, html)
- self.container_page.edit()
- self.html_editor.open_raw_editor()
- editor_value = self.html_editor.editor_value
- self.assertEqual(value, editor_value)
-
- def test_font_selection_dropdown(self):
- """
- Scenario: Font selection dropdown contains Default font and tinyMCE builtin fonts
- Given I have created a Blank HTML Page
- When I edit the page
- And I click font selection dropdown
- Then I should see a list of available fonts
- And "Default" fonts should be available
- And all standard tinyMCE fonts should be available
- """
- # Add HTML Text type component
- self._add_component('Text')
- self.container_page.edit()
- EXPECTED_FONTS = {
- u"Default": [u'"Open Sans"', u'Verdana', u'Arial', u'Helvetica', u'sans-serif'],
- u"Andale Mono": [u'andale mono', u'times'],
- u"Arial": [u'arial', u'helvetica', u'sans-serif'],
- u"Arial Black": [u'arial black', u'avant garde'],
- u"Book Antiqua": [u'book antiqua', u'palatino'],
- u"Comic Sans MS": [u'comic sans ms', u'sans-serif'],
- u"Courier New": [u'courier new', u'courier'],
- u"Georgia": [u'georgia', u'palatino'],
- u"Helvetica": [u'helvetica'],
- u"Impact": [u'impact', u'chicago'],
- u"Symbol": [u'symbol'],
- u"Tahoma": [u'tahoma', u'arial', u'helvetica', u'sans-serif'],
- u"Terminal": [u'terminal', u'monaco'],
- u"Times New Roman": [u'times new roman', u'times'],
- u"Trebuchet MS": [u'trebuchet ms', u'geneva'],
- u"Verdana": [u'verdana', u'geneva'],
- # tinyMCE does not set font-family on dropdown span for these two fonts
- u"Webdings": [u""], # webdings
- u"Wingdings": [u""] # wingdings
- }
- self.html_editor.open_font_dropdown()
- self.assertDictContainsSubset(EXPECTED_FONTS, self.html_editor.font_dict())
-
- def test_image_modal(self):
- """
- Scenario: TinyMCE text editor allows to add multiple images.
-
- Given I have created a Blank text editor Page.
- I add an image in TinyMCE text editor and hit save button.
- I edit the component again.
- I add another image in TinyMCE text editor and hit save button again.
- Then it is expected that both images show up on page.
- """
- image_file_names = [u'file-0.png', u'file-1.png']
- self._add_component('Text')
-
- for image in image_file_names:
- image_path = os.path.join(UPLOAD_FILE_DIR, image)
- self.container_page.edit()
- self.html_editor.open_image_modal()
- self.html_editor.upload_image(image_path)
- self.html_editor.save_content()
- self.html_editor.wait_for_ajax()
-
- self.container_page.edit()
- self.html_editor.open_raw_editor()
- editor_value = self.html_editor.editor_value
- number_of_images = editor_value.count(u'img')
- self.assertEqual(number_of_images, 2)
diff --git a/common/test/acceptance/tests/studio/test_studio_library.py b/common/test/acceptance/tests/studio/test_studio_library.py
index aaf720c9f0..3ae657c0d3 100644
--- a/common/test/acceptance/tests/studio/test_studio_library.py
+++ b/common/test/acceptance/tests/studio/test_studio_library.py
@@ -3,150 +3,11 @@ Acceptance tests for Content Libraries in Studio
"""
-from common.test.acceptance.pages.common.auto_auth import AutoAuthPage
from common.test.acceptance.pages.studio.library import LibraryEditPage
-from common.test.acceptance.pages.studio.users import LibraryUsersPage
from common.test.acceptance.tests.studio.base_studio_test import StudioLibraryTest
from openedx.core.lib.tests import attr
-@attr(shard=21)
-class LibraryUsersPageTest(StudioLibraryTest):
- """
- Test the functionality of the library "Instructor Access" page.
- """
- def setUp(self):
- super(LibraryUsersPageTest, self).setUp()
-
- # Create a second user for use in these tests:
- AutoAuthPage(self.browser, username="second", email="second@example.com", no_login=True).visit()
-
- self.page = LibraryUsersPage(self.browser, self.library_key)
- self.page.visit()
-
- def _refresh_page(self):
- """
- Reload the page.
- """
- self.page = LibraryUsersPage(self.browser, self.library_key)
- self.page.visit()
-
- def test_user_management(self):
- """
- Scenario: Ensure that we can edit the permissions of users.
- Given I have a library in Studio where I am the only admin
- assigned (which is the default for a newly-created library)
- And I navigate to Library "Instructor Access" Page in Studio
- Then there should be one user listed (myself), and I must
- not be able to remove myself or my instructor privilege.
-
- When I click Add Instructor
- Then I see a form to complete
- When I complete the form and submit it
- Then I can see the new user is listed as a "User" of the library
-
- When I click to Add Staff permissions to the new user
- Then I can see the new user has staff permissions and that I am now
- able to promote them to an Admin or remove their staff permissions.
-
- When I click to Add Admin permissions to the new user
- Then I can see the new user has admin permissions and that I can now
- remove Admin permissions from either user.
- """
- def check_is_only_admin(user):
- """
- Ensure user is an admin user and cannot be removed.
- (There must always be at least one admin user.)
- """
- self.assertIn("admin", user.role_label.lower())
- self.assertFalse(user.can_promote)
- self.assertFalse(user.can_demote)
- self.assertFalse(user.can_delete)
- self.assertTrue(user.has_no_change_warning)
- self.assertIn("Promote another member to Admin to remove your admin rights", user.no_change_warning_text)
-
- self.assertEqual(len(self.page.users), 1)
- user = self.page.users[0]
- self.assertTrue(user.is_current_user)
- check_is_only_admin(user)
-
- # Add a new user:
-
- self.assertTrue(self.page.has_add_button)
- self.assertFalse(self.page.new_user_form_visible)
- self.page.click_add_button()
- self.assertTrue(self.page.new_user_form_visible)
- self.page.set_new_user_email('second@example.com')
- self.page.click_submit_new_user_form()
-
- # Check the new user's listing:
-
- def get_two_users():
- """
- Expect two users to be listed, one being me, and another user.
- Returns me, them
- """
- users = self.page.users
- self.assertEqual(len(users), 2)
- self.assertEqual(len([u for u in users if u.is_current_user]), 1)
- if users[0].is_current_user:
- return users[0], users[1]
- else:
- return users[1], users[0]
-
- self._refresh_page()
- user_me, them = get_two_users()
- check_is_only_admin(user_me)
-
- self.assertIn("user", them.role_label.lower())
- self.assertTrue(them.can_promote)
- self.assertIn("Add Staff Access", them.promote_button_text)
- self.assertFalse(them.can_demote)
- self.assertTrue(them.can_delete)
- self.assertFalse(them.has_no_change_warning)
-
- # Add Staff permissions to the new user:
-
- them.click_promote()
- self._refresh_page()
- user_me, them = get_two_users()
- check_is_only_admin(user_me)
-
- self.assertIn("staff", them.role_label.lower())
- self.assertTrue(them.can_promote)
- self.assertIn("Add Admin Access", them.promote_button_text)
- self.assertTrue(them.can_demote)
- self.assertIn("Remove Staff Access", them.demote_button_text)
- self.assertTrue(them.can_delete)
- self.assertFalse(them.has_no_change_warning)
-
- # Add Admin permissions to the new user:
-
- them.click_promote()
- self._refresh_page()
- user_me, them = get_two_users()
- self.assertIn("admin", user_me.role_label.lower())
- self.assertFalse(user_me.can_promote)
- self.assertTrue(user_me.can_demote)
- self.assertTrue(user_me.can_delete)
- self.assertFalse(user_me.has_no_change_warning)
-
- self.assertIn("admin", them.role_label.lower())
- self.assertFalse(them.can_promote)
- self.assertTrue(them.can_demote)
- self.assertIn("Remove Admin Access", them.demote_button_text)
- self.assertTrue(them.can_delete)
- self.assertFalse(them.has_no_change_warning)
-
- # Delete the new user:
-
- them.click_delete()
- self._refresh_page()
- self.assertEqual(len(self.page.users), 1)
- user = self.page.users[0]
- self.assertTrue(user.is_current_user)
-
-
@attr('a11y')
class StudioLibraryA11yTest(StudioLibraryTest):
"""
diff --git a/common/test/acceptance/tests/studio/test_studio_library_container.py b/common/test/acceptance/tests/studio/test_studio_library_container.py
deleted file mode 100644
index 38c459626d..0000000000
--- a/common/test/acceptance/tests/studio/test_studio_library_container.py
+++ /dev/null
@@ -1,176 +0,0 @@
-"""
-Acceptance tests for Library Content in LMS
-"""
-
-
-import textwrap
-
-import ddt
-import six
-
-from common.test.acceptance.fixtures.course import CourseFixture, XBlockFixtureDesc
-from common.test.acceptance.pages.studio.library import StudioLibraryContainerXBlockWrapper, StudioLibraryContentEditor
-from common.test.acceptance.pages.studio.overview import CourseOutlinePage
-from common.test.acceptance.tests.helpers import TestWithSearchIndexMixin, UniqueCourseTest
-from common.test.acceptance.tests.studio.base_studio_test import StudioLibraryTest
-
-SECTION_NAME = 'Test Section'
-SUBSECTION_NAME = 'Test Subsection'
-UNIT_NAME = 'Test Unit'
-
-
-@ddt.ddt
-class StudioLibraryContainerTest(StudioLibraryTest, UniqueCourseTest, TestWithSearchIndexMixin):
- """
- Test Library Content block in LMS
- """
- shard = 17
-
- def setUp(self):
- """
- Install library with some content and a course using fixtures
- """
- self._create_search_index()
- super(StudioLibraryContainerTest, self).setUp()
- # Also create a course:
- self.course_fixture = CourseFixture(
- self.course_info['org'], self.course_info['number'],
- self.course_info['run'], self.course_info['display_name']
- )
- self.populate_course_fixture(self.course_fixture)
- self.course_fixture.install()
- self.outline = CourseOutlinePage(
- self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run']
- )
-
- self.outline.visit()
- subsection = self.outline.section(SECTION_NAME).subsection(SUBSECTION_NAME)
- self.unit_page = subsection.expand_subsection().unit(UNIT_NAME).go_to()
-
- def tearDown(self):
- """ Tear down method: remove search index backing file """
- self._cleanup_index_file()
- super(StudioLibraryContainerTest, self).tearDown()
-
- def populate_library_fixture(self, library_fixture):
- """
- Populate the children of the test course fixture.
- """
- library_fixture.add_children(
- XBlockFixtureDesc("html", "Html1"),
- XBlockFixtureDesc("html", "Html2"),
- XBlockFixtureDesc("html", "Html3"),
- )
-
- def populate_course_fixture(self, course_fixture):
- """ Install a course with sections/problems, tabs, updates, and handouts """
- library_content_metadata = {
- 'source_library_id': six.text_type(self.library_key),
- 'mode': 'random',
- 'max_count': 1,
- }
-
- course_fixture.add_children(
- XBlockFixtureDesc('chapter', SECTION_NAME).add_children(
- XBlockFixtureDesc('sequential', SUBSECTION_NAME).add_children(
- XBlockFixtureDesc('vertical', UNIT_NAME).add_children(
- XBlockFixtureDesc('library_content', "Library Content", metadata=library_content_metadata)
- )
- )
- )
- )
-
- def _get_library_xblock_wrapper(self, xblock):
- """
- Wraps xblock into :class:`...pages.studio.library.StudioLibraryContainerXBlockWrapper`
- """
- return StudioLibraryContainerXBlockWrapper.from_xblock_wrapper(xblock)
-
- @ddt.data(1, 2, 3)
- def test_can_edit_metadata(self, max_count):
- """
- Scenario: Given I have a library, a course and library content xblock in a course
- When I go to studio unit page for library content block
- And I edit library content metadata and save it
- Then I can ensure that data is persisted
- """
- library_name = self.library_info['display_name']
- library_container = self._get_library_xblock_wrapper(self.unit_page.xblocks[1])
- library_container.edit()
- edit_modal = StudioLibraryContentEditor(self.browser, library_container.locator)
- edit_modal.library_name = library_name
- edit_modal.count = max_count
-
- library_container.save_settings() # saving settings
-
- # open edit window again to verify changes are persistent
- library_container.edit()
- edit_modal = StudioLibraryContentEditor(self.browser, library_container.locator)
- self.assertEqual(edit_modal.library_name, library_name)
- self.assertEqual(edit_modal.count, max_count)
-
- def test_no_content_message(self):
- """
- Scenario: Given I have a library, a course and library content xblock in a course
- When I go to studio unit page for library content block
- And I set Problem Type selector so that no libraries have matching content
- Then I can see that "No matching content" warning is shown
- When I set Problem Type selector so that there is matching content
- Then I can see that warning messages are not shown
- """
- # Add a single "Dropdown" type problem to the library (which otherwise has only HTML blocks):
- self.library_fixture.create_xblock(self.library_fixture.library_location, XBlockFixtureDesc(
- "problem", "Dropdown",
- data=textwrap.dedent("""
- - - """) - )) - - expected_text = 'There are no matching problem types in the specified libraries. Select another problem type' - - library_container = self._get_library_xblock_wrapper(self.unit_page.xblocks[1]) - - # precondition check - assert library has children matching filter criteria - self.assertFalse(library_container.has_validation_error) - self.assertFalse(library_container.has_validation_warning) - - library_container.edit() - edit_modal = StudioLibraryContentEditor(self.browser, library_container.locator) - self.assertEqual(edit_modal.capa_type, "Any Type") # precondition check - edit_modal.capa_type = "Custom Evaluated Script" - - library_container.save_settings() - - self.assertTrue(library_container.has_validation_warning) - self.assertIn(expected_text, library_container.validation_warning_text) - - library_container.edit() - edit_modal = StudioLibraryContentEditor(self.browser, library_container.locator) - self.assertEqual(edit_modal.capa_type, "Custom Evaluated Script") # precondition check - edit_modal.capa_type = "Dropdown" - library_container.save_settings() - - # Library should contain single Dropdown problem, so now there should be no errors again - self.assertFalse(library_container.has_validation_error) - self.assertFalse(library_container.has_validation_warning) - - def test_cannot_manage(self): - """ - Scenario: Given I have a library, a course and library content xblock in a course - When I go to studio unit page for library content block - And when I click the "View" link - Then I can see a preview of the blocks drawn from the library. - - And I do not see a duplicate button - And I do not see a delete button - """ - block_wrapper_unit_page = self._get_library_xblock_wrapper(self.unit_page.xblocks[0].children[0]) - container_page = block_wrapper_unit_page.go_to_container() - - for block in container_page.xblocks: - self.assertFalse(block.has_duplicate_button) - self.assertFalse(block.has_delete_button) - self.assertFalse(block.has_edit_visibility_button) diff --git a/common/test/acceptance/tests/studio/test_studio_outline.py b/common/test/acceptance/tests/studio/test_studio_outline.py deleted file mode 100644 index f089114ed2..0000000000 --- a/common/test/acceptance/tests/studio/test_studio_outline.py +++ /dev/null @@ -1,1983 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Acceptance tests for studio related to the outline page. -""" - - -import itertools -import json -from datetime import datetime, timedelta -from unittest import skip - -from pytz import UTC -import six -from six.moves import range - -from common.test.acceptance.fixtures.config import ConfigModelFixture -from common.test.acceptance.fixtures.course import XBlockFixtureDesc -from common.test.acceptance.pages.lms.course_home import CourseHomePage -from common.test.acceptance.pages.lms.courseware import CoursewarePage -from common.test.acceptance.pages.lms.progress import ProgressPage -from common.test.acceptance.pages.studio.checklists import CourseChecklistsPage -from common.test.acceptance.pages.studio.overview import ContainerPage, CourseOutlinePage, ExpandCollapseLinkState -from common.test.acceptance.pages.studio.settings import SettingsPage -from common.test.acceptance.pages.studio.settings_advanced import AdvancedSettingsPage -from common.test.acceptance.pages.studio.settings_group_configurations import GroupConfigurationsPage -from common.test.acceptance.pages.studio.utils import add_discussion, drag, verify_ordering -from common.test.acceptance.tests.helpers import disable_animations, load_data_str -from openedx.core.lib.tests import attr - -from .base_studio_test import StudioCourseTest - -SECTION_NAME = 'Test Section' -SUBSECTION_NAME = 'Test Subsection' -UNIT_NAME = 'Test Unit' - - -class CourseOutlineTest(StudioCourseTest): - """ - Base class for all course outline tests - """ - - def setUp(self): - """ - Install a course with no content using a fixture. - """ - super(CourseOutlineTest, self).setUp() - self.course_outline_page = CourseOutlinePage( - self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run'] - ) - self.advanced_settings = AdvancedSettingsPage( - self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run'] - ) - - def populate_course_fixture(self, course_fixture): - """ Install a course with sections/problems, tabs, updates, and handouts """ - course_fixture.add_children( - XBlockFixtureDesc('chapter', SECTION_NAME).add_children( - XBlockFixtureDesc('sequential', SUBSECTION_NAME).add_children( - XBlockFixtureDesc('vertical', UNIT_NAME).add_children( - XBlockFixtureDesc('problem', 'Test Problem 1', data=load_data_str('multiple_choice.xml')), - XBlockFixtureDesc('html', 'Test HTML Component'), - XBlockFixtureDesc('discussion', 'Test Discussion Component') - ) - ) - ) - ) - - def do_action_and_verify(self, outline_page, action, expected_ordering): - """ - Perform the supplied action and then verify the resulting ordering. - """ - if outline_page is None: - outline_page = self.course_outline_page.visit() - - action(outline_page) - verify_ordering(self, outline_page, expected_ordering) - - # Reload the page and expand all subsections to see that the change was persisted. - outline_page = self.course_outline_page.visit() - outline_page.q(css='.outline-item.outline-subsection.is-collapsed .ui-toggle-expansion').click() - verify_ordering(self, outline_page, expected_ordering) - - -@attr(shard=20) -class CourseOutlineDragAndDropTest(CourseOutlineTest): - """ - Tests of drag and drop within the outline page. - """ - __test__ = True - - def populate_course_fixture(self, course_fixture): - """ - Create a course with one section, two subsections, and four units - """ - # with collapsed outline - self.chap_1_handle = 0 - self.chap_1_seq_1_handle = 1 - - # with first sequential expanded - self.seq_1_vert_1_handle = 2 - self.seq_1_vert_2_handle = 3 - self.chap_1_seq_2_handle = 4 - - course_fixture.add_children( - XBlockFixtureDesc('chapter', "1").add_children( - XBlockFixtureDesc('sequential', '1.1').add_children( - XBlockFixtureDesc('vertical', '1.1.1'), - XBlockFixtureDesc('vertical', '1.1.2') - ), - XBlockFixtureDesc('sequential', '1.2').add_children( - XBlockFixtureDesc('vertical', '1.2.1'), - XBlockFixtureDesc('vertical', '1.2.2') - ) - ) - ) - - def drag_and_verify(self, source, target, expected_ordering, outline_page=None): - self.do_action_and_verify( - outline_page, - lambda outline: drag(outline, source, target), - expected_ordering - ) - - @skip("Fails in Firefox 45 but passes in Chrome") - def test_drop_unit_in_collapsed_subsection(self): - """ - Drag vertical "1.1.2" from subsection "1.1" into collapsed subsection "1.2" which already - have its own verticals. - """ - course_outline_page = self.course_outline_page.visit() - # expand first subsection - course_outline_page.q(css='.outline-item.outline-subsection.is-collapsed .ui-toggle-expansion').first.click() - - expected_ordering = [{"1": ["1.1", "1.2"]}, - {"1.1": ["1.1.1"]}, - {"1.2": ["1.1.2", "1.2.1", "1.2.2"]}] - self.drag_and_verify(self.seq_1_vert_2_handle, self.chap_1_seq_2_handle, expected_ordering, course_outline_page) - - -@attr(shard=3) -class WarningMessagesTest(CourseOutlineTest): - """ - Feature: Warning messages on sections, subsections, and units - """ - - __test__ = True - - STAFF_ONLY_WARNING = 'Contains staff only content' - LIVE_UNPUBLISHED_WARNING = 'Unpublished changes to live content' - FUTURE_UNPUBLISHED_WARNING = 'Unpublished changes to content that will release in the future' - NEVER_PUBLISHED_WARNING = 'Unpublished units will not be released' - - class PublishState(object): - """ - Default values for representing the published state of a unit - """ - NEVER_PUBLISHED = 1 - UNPUBLISHED_CHANGES = 2 - PUBLISHED = 3 - VALUES = [NEVER_PUBLISHED, UNPUBLISHED_CHANGES, PUBLISHED] - - class UnitState(object): - """ Represents the state of a unit """ - - def __init__(self, is_released, publish_state, is_locked): - """ Creates a new UnitState with the given properties """ - self.is_released = is_released - self.publish_state = publish_state - self.is_locked = is_locked - - @property - def name(self): - """ Returns an appropriate name based on the properties of the unit """ - result = "Released " if self.is_released else "Unreleased " - if self.publish_state == WarningMessagesTest.PublishState.NEVER_PUBLISHED: - result += "Never Published " - elif self.publish_state == WarningMessagesTest.PublishState.UNPUBLISHED_CHANGES: - result += "Unpublished Changes " - else: - result += "Published " - result += "Locked" if self.is_locked else "Unlocked" - return result - - def populate_course_fixture(self, course_fixture): - """ Install a course with various configurations that could produce warning messages """ - - # Define the dimensions that map to the UnitState constructor - features = [ - [True, False], # Possible values for is_released - self.PublishState.VALUES, # Possible values for publish_state - [True, False] # Possible values for is_locked - ] - - # Add a fixture for every state in the product of features - course_fixture.add_children(*[ - self._build_fixture(self.UnitState(*state)) for state in itertools.product(*features) - ]) - - def _build_fixture(self, unit_state): - """ Returns an XBlockFixtureDesc with a section, subsection, and possibly unit that has the given state. """ - name = unit_state.name - start = (datetime(1984, 3, 4) if unit_state.is_released else datetime.now(UTC) + timedelta(1)).isoformat() - - subsection = XBlockFixtureDesc('sequential', name, metadata={'start': start}) - - # Children of never published subsections will be added on demand via _ensure_unit_present - return XBlockFixtureDesc('chapter', name).add_children( - subsection if unit_state.publish_state == self.PublishState.NEVER_PUBLISHED - else subsection.add_children( - XBlockFixtureDesc('vertical', name, metadata={ - 'visible_to_staff_only': True if unit_state.is_locked else None - }) - ) - ) - - def test_released_never_published_locked(self): - """ Tests that released never published locked units display staff only warnings """ - self._verify_unit_warning( - self.UnitState(is_released=True, publish_state=self.PublishState.NEVER_PUBLISHED, is_locked=True), - self.STAFF_ONLY_WARNING - ) - - def test_released_never_published_unlocked(self): - """ Tests that released never published unlocked units display 'Unpublished units will not be released' """ - self._verify_unit_warning( - self.UnitState(is_released=True, publish_state=self.PublishState.NEVER_PUBLISHED, is_locked=False), - self.NEVER_PUBLISHED_WARNING - ) - - def test_released_unpublished_changes_locked(self): - """ Tests that released unpublished changes locked units display staff only warnings """ - self._verify_unit_warning( - self.UnitState(is_released=True, publish_state=self.PublishState.UNPUBLISHED_CHANGES, is_locked=True), - self.STAFF_ONLY_WARNING - ) - - def test_released_unpublished_changes_unlocked(self): - """ Tests that released unpublished changes unlocked units display 'Unpublished changes to live content' """ - self._verify_unit_warning( - self.UnitState(is_released=True, publish_state=self.PublishState.UNPUBLISHED_CHANGES, is_locked=False), - self.LIVE_UNPUBLISHED_WARNING - ) - - def test_released_published_locked(self): - """ Tests that released published locked units display staff only warnings """ - self._verify_unit_warning( - self.UnitState(is_released=True, publish_state=self.PublishState.PUBLISHED, is_locked=True), - self.STAFF_ONLY_WARNING - ) - - def test_released_published_unlocked(self): - """ Tests that released published unlocked units display no warnings """ - self._verify_unit_warning( - self.UnitState(is_released=True, publish_state=self.PublishState.PUBLISHED, is_locked=False), - None - ) - - def test_unreleased_never_published_locked(self): - """ Tests that unreleased never published locked units display staff only warnings """ - self._verify_unit_warning( - self.UnitState(is_released=False, publish_state=self.PublishState.NEVER_PUBLISHED, is_locked=True), - self.STAFF_ONLY_WARNING - ) - - def test_unreleased_never_published_unlocked(self): - """ Tests that unreleased never published unlocked units display 'Unpublished units will not be released' """ - self._verify_unit_warning( - self.UnitState(is_released=False, publish_state=self.PublishState.NEVER_PUBLISHED, is_locked=False), - self.NEVER_PUBLISHED_WARNING - ) - - def test_unreleased_unpublished_changes_locked(self): - """ Tests that unreleased unpublished changes locked units display staff only warnings """ - self._verify_unit_warning( - self.UnitState(is_released=False, publish_state=self.PublishState.UNPUBLISHED_CHANGES, is_locked=True), - self.STAFF_ONLY_WARNING - ) - - def test_unreleased_unpublished_changes_unlocked(self): - """ - Tests that unreleased unpublished changes unlocked units display 'Unpublished changes to content that will - release in the future' - """ - self._verify_unit_warning( - self.UnitState(is_released=False, publish_state=self.PublishState.UNPUBLISHED_CHANGES, is_locked=False), - self.FUTURE_UNPUBLISHED_WARNING - ) - - def test_unreleased_published_locked(self): - """ Tests that unreleased published locked units display staff only warnings """ - self._verify_unit_warning( - self.UnitState(is_released=False, publish_state=self.PublishState.PUBLISHED, is_locked=True), - self.STAFF_ONLY_WARNING - ) - - def test_unreleased_published_unlocked(self): - """ Tests that unreleased published unlocked units display no warnings """ - self._verify_unit_warning( - self.UnitState(is_released=False, publish_state=self.PublishState.PUBLISHED, is_locked=False), - None - ) - - def _verify_unit_warning(self, unit_state, expected_status_message): - """ - Verifies that the given unit's messages match the expected messages. - If expected_status_message is None, then the unit status message is expected to not be present. - """ - self._ensure_unit_present(unit_state) - self.course_outline_page.visit() - section = self.course_outline_page.section(unit_state.name) - subsection = section.subsection_at(0) - subsection.expand_subsection() - unit = subsection.unit_at(0) - if expected_status_message == self.STAFF_ONLY_WARNING: - self.assertEqual(section.status_message, self.STAFF_ONLY_WARNING) - self.assertEqual(subsection.status_message, self.STAFF_ONLY_WARNING) - self.assertEqual(unit.status_message, self.STAFF_ONLY_WARNING) - else: - self.assertFalse(section.has_status_message) - self.assertFalse(subsection.has_status_message) - if expected_status_message: - self.assertEqual(unit.status_message, expected_status_message) - else: - self.assertFalse(unit.has_status_message) - - def _ensure_unit_present(self, unit_state): - """ Ensures that a unit with the given state is present on the course outline """ - if unit_state.publish_state == self.PublishState.PUBLISHED: - return - - name = unit_state.name - self.course_outline_page.visit() - subsection = self.course_outline_page.section(name).subsection(name) - subsection.expand_subsection() - - if unit_state.publish_state == self.PublishState.UNPUBLISHED_CHANGES: - unit = subsection.unit(name).go_to() - add_discussion(unit) - elif unit_state.publish_state == self.PublishState.NEVER_PUBLISHED: - subsection.add_unit() - unit = ContainerPage(self.browser, None) - unit.wait_for_page() - unit.set_name(name) - - if unit.is_staff_locked != unit_state.is_locked: - unit.toggle_staff_lock() - - -@attr(shard=3) -class EditingSectionsTest(CourseOutlineTest): - """ - Feature: Editing Release date, Due date and grading type. - """ - - __test__ = True - - def test_can_edit_subsection(self): - """ - Scenario: I can edit settings of subsection. - - Given that I have created a subsection - Then I see release date, due date and grading policy of subsection in course outline - When I click on the configuration icon - Then edit modal window is shown - And release date, due date and grading policy fields present - And they have correct initial values - Then I set new values for these fields - And I click save button on the modal - Then I see release date, due date and grading policy of subsection in course outline - """ - self.course_outline_page.visit() - subsection = self.course_outline_page.section(SECTION_NAME).subsection(SUBSECTION_NAME) - - # Verify that Release date visible by default - self.assertTrue(subsection.release_date) - # Verify that Due date and Policy hidden by default - self.assertFalse(subsection.due_date) - self.assertFalse(subsection.policy) - - modal = subsection.edit() - - # Verify fields - self.assertTrue(modal.has_release_date()) - self.assertTrue(modal.has_release_time()) - self.assertTrue(modal.has_due_date()) - self.assertTrue(modal.has_due_time()) - self.assertTrue(modal.has_policy()) - - # Verify initial values - self.assertEqual(modal.release_date, u'1/1/1970') - self.assertEqual(modal.release_time, u'00:00') - self.assertEqual(modal.due_date, u'') - self.assertEqual(modal.due_time, u'') - self.assertEqual(modal.policy, u'Not Graded') - - # Set new values - modal.release_date = '3/12/1972' - modal.release_time = '04:01' - modal.due_date = '7/21/2014' - modal.due_time = '23:39' - modal.policy = 'Lab' - - modal.save() - self.assertIn(u'Released: Mar 12, 1972', subsection.release_date) - self.assertIn(u'04:01', subsection.release_date) - self.assertIn(u'Due: Jul 21, 2014', subsection.due_date) - self.assertIn(u'23:39', subsection.due_date) - self.assertIn(u'Lab', subsection.policy) - - def test_can_edit_section(self): - """ - Scenario: I can edit settings of section. - - Given that I have created a section - Then I see release date of section in course outline - When I click on the configuration icon - Then edit modal window is shown - And release date field present - And it has correct initial value - Then I set new value for this field - And I click save button on the modal - Then I see release date of section in course outline - """ - self.course_outline_page.visit() - section = self.course_outline_page.section(SECTION_NAME) - - # Verify that Release date visible by default - self.assertTrue(section.release_date) - # Verify that Due date and Policy are not present - self.assertFalse(section.due_date) - self.assertFalse(section.policy) - - modal = section.edit() - # Verify fields - self.assertTrue(modal.has_release_date()) - self.assertFalse(modal.has_due_date()) - self.assertFalse(modal.has_policy()) - - # Verify initial value - self.assertEqual(modal.release_date, u'1/1/1970') - - # Set new value - modal.release_date = '5/14/1969' - - modal.save() - self.assertIn(u'Released: May 14, 1969', section.release_date) - # Verify that Due date and Policy are not present - self.assertFalse(section.due_date) - self.assertFalse(section.policy) - - def test_subsection_is_graded_in_lms(self): - """ - Scenario: I can grade subsection from course outline page. - - Given I visit progress page - And I see that problem in subsection has grading type "Practice" - Then I visit course outline page - And I click on the configuration icon of subsection - And I set grading policy to "Lab" - And I click save button on the modal - Then I visit progress page - And I see that problem in subsection has grading type "Problem" - """ - progress_page = ProgressPage(self.browser, self.course_id) - progress_page.visit() - progress_page.wait_for_page() - self.assertEqual(u'Practice', progress_page.grading_formats[0]) - self.course_outline_page.visit() - - subsection = self.course_outline_page.section(SECTION_NAME).subsection(SUBSECTION_NAME) - modal = subsection.edit() - # Set new values - modal.policy = 'Lab' - modal.save() - - progress_page.visit() - - self.assertEqual(u'Problem', progress_page.grading_formats[0]) - - def test_unchanged_release_date_is_not_saved(self): - """ - Scenario: Saving a subsection without changing the release date will not override the release date - Given that I have created a section with a subsection - When I open the settings modal for the subsection - And I pressed save - And I open the settings modal for the section - And I change the release date to 07/20/1969 - And I press save - Then the subsection and the section have the release date 07/20/1969 - """ - self.course_outline_page.visit() - - modal = self.course_outline_page.section_at(0).subsection_at(0).edit() - modal.save() - - modal = self.course_outline_page.section_at(0).edit() - modal.release_date = '7/20/1969' - modal.save() - - release_text = 'Released: Jul 20, 1969' - self.assertIn(release_text, self.course_outline_page.section_at(0).release_date) - self.assertIn(release_text, self.course_outline_page.section_at(0).subsection_at(0).release_date) - - -@attr(shard=3) -class UnitAccessTest(CourseOutlineTest): - """ - Feature: Units can be restricted and unrestricted to certain groups from the course outline. - """ - - __test__ = True - - def setUp(self): - super(UnitAccessTest, self).setUp() - self.group_configurations_page = GroupConfigurationsPage( - self.browser, - self.course_info['org'], - self.course_info['number'], - self.course_info['run'] - ) - self.content_group_a = "Test Group A" - self.content_group_b = "Test Group B" - - self.group_configurations_page.visit() - self.group_configurations_page.create_first_content_group() - config_a = self.group_configurations_page.content_groups[0] - config_a.name = self.content_group_a - config_a.save() - self.content_group_a_id = config_a.id - - self.group_configurations_page.add_content_group() - config_b = self.group_configurations_page.content_groups[1] - config_b.name = self.content_group_b - config_b.save() - self.content_group_b_id = config_b.id - - def populate_course_fixture(self, course_fixture): - """ - Create a course with one section, one subsection, and two units - """ - # with collapsed outline - self.chap_1_handle = 0 - self.chap_1_seq_1_handle = 1 - - # with first sequential expanded - self.seq_1_vert_1_handle = 2 - self.seq_1_vert_2_handle = 3 - self.chap_1_seq_2_handle = 4 - - course_fixture.add_children( - XBlockFixtureDesc('chapter', "1").add_children( - XBlockFixtureDesc('sequential', '1.1').add_children( - XBlockFixtureDesc('vertical', '1.1.1'), - XBlockFixtureDesc('vertical', '1.1.2') - ) - ) - ) - - -@attr(shard=14) -class StaffLockTest(CourseOutlineTest): - """ - Feature: Sections, subsections, and units can be locked and unlocked from the course outline. - """ - - __test__ = True - - def populate_course_fixture(self, course_fixture): - """ Create a course with one section, two subsections, and four units """ - course_fixture.add_children( - XBlockFixtureDesc('chapter', '1').add_children( - XBlockFixtureDesc('sequential', '1.1').add_children( - XBlockFixtureDesc('vertical', '1.1.1'), - XBlockFixtureDesc('vertical', '1.1.2') - ), - XBlockFixtureDesc('sequential', '1.2').add_children( - XBlockFixtureDesc('vertical', '1.2.1'), - XBlockFixtureDesc('vertical', '1.2.2') - ) - ) - ) - - def _verify_descendants_are_staff_only(self, item): - """Verifies that all the descendants of item are staff only""" - self.assertTrue(item.is_staff_only) - if hasattr(item, 'children'): - for child in item.children(): - self._verify_descendants_are_staff_only(child) - - def _remove_staff_lock_and_verify_warning(self, outline_item, expect_warning): - """Removes staff lock from a course outline item and checks whether or not a warning appears.""" - modal = outline_item.edit() - modal.is_explicitly_locked = False - if expect_warning: - self.assertTrue(modal.shows_staff_lock_warning()) - else: - self.assertFalse(modal.shows_staff_lock_warning()) - modal.save() - - def _toggle_lock_on_unlocked_item(self, outline_item): - """Toggles outline_item's staff lock on and then off, verifying the staff lock warning""" - self.assertFalse(outline_item.has_staff_lock_warning) - outline_item.set_staff_lock(True) - self.assertTrue(outline_item.has_staff_lock_warning) - self._verify_descendants_are_staff_only(outline_item) - outline_item.set_staff_lock(False) - self.assertFalse(outline_item.has_staff_lock_warning) - - def _verify_explicit_staff_lock_remains_after_unlocking_parent(self, child_item, parent_item): - """Verifies that child_item's explicit staff lock remains after removing parent_item's staff lock""" - child_item.set_staff_lock(True) - parent_item.set_staff_lock(True) - self.assertTrue(parent_item.has_staff_lock_warning) - self.assertTrue(child_item.has_staff_lock_warning) - parent_item.set_staff_lock(False) - self.assertFalse(parent_item.has_staff_lock_warning) - self.assertTrue(child_item.has_staff_lock_warning) - - def test_units_can_be_locked(self): - """ - Scenario: Units can be locked and unlocked from the course outline page - Given I have a course with a unit - When I click on the configuration icon - And I enable explicit staff locking - And I click save - Then the unit shows a staff lock warning - And when I click on the configuration icon - And I disable explicit staff locking - And I click save - Then the unit does not show a staff lock warning - """ - self.course_outline_page.visit() - self.course_outline_page.expand_all_subsections() - unit = self.course_outline_page.section_at(0).subsection_at(0).unit_at(0) - self._toggle_lock_on_unlocked_item(unit) - - def test_subsections_can_be_locked(self): - """ - Scenario: Subsections can be locked and unlocked from the course outline page - Given I have a course with a subsection - When I click on the subsection's configuration icon - And I enable explicit staff locking - And I click save - Then the subsection shows a staff lock warning - And all its descendants are staff locked - And when I click on the subsection's configuration icon - And I disable explicit staff locking - And I click save - Then the the subsection does not show a staff lock warning - """ - self.course_outline_page.visit() - self.course_outline_page.expand_all_subsections() - subsection = self.course_outline_page.section_at(0).subsection_at(0) - self._toggle_lock_on_unlocked_item(subsection) - - def test_sections_can_be_locked(self): - """ - Scenario: Sections can be locked and unlocked from the course outline page - Given I have a course with a section - When I click on the section's configuration icon - And I enable explicit staff locking - And I click save - Then the section shows a staff lock warning - And all its descendants are staff locked - And when I click on the section's configuration icon - And I disable explicit staff locking - And I click save - Then the section does not show a staff lock warning - """ - self.course_outline_page.visit() - self.course_outline_page.expand_all_subsections() - section = self.course_outline_page.section_at(0) - self._toggle_lock_on_unlocked_item(section) - - def test_explicit_staff_lock_remains_after_unlocking_section(self): - """ - Scenario: An explicitly locked unit is still locked after removing an inherited lock from a section - Given I have a course with sections, subsections, and units - And I have enabled explicit staff lock on a section and one of its units - When I click on the section's configuration icon - And I disable explicit staff locking - And I click save - Then the unit still shows a staff lock warning - """ - self.course_outline_page.visit() - self.course_outline_page.expand_all_subsections() - section = self.course_outline_page.section_at(0) - unit = section.subsection_at(0).unit_at(0) - self._verify_explicit_staff_lock_remains_after_unlocking_parent(unit, section) - - def test_explicit_staff_lock_remains_after_unlocking_subsection(self): - """ - Scenario: An explicitly locked unit is still locked after removing an inherited lock from a subsection - Given I have a course with sections, subsections, and units - And I have enabled explicit staff lock on a subsection and one of its units - When I click on the subsection's configuration icon - And I disable explicit staff locking - And I click save - Then the unit still shows a staff lock warning - """ - self.course_outline_page.visit() - self.course_outline_page.expand_all_subsections() - subsection = self.course_outline_page.section_at(0).subsection_at(0) - unit = subsection.unit_at(0) - self._verify_explicit_staff_lock_remains_after_unlocking_parent(unit, subsection) - - def test_section_displays_lock_when_all_subsections_locked(self): - """ - Scenario: All subsections in section are explicitly locked, section should display staff only warning - Given I have a course one section and two subsections - When I enable explicit staff lock on all the subsections - Then the section shows a staff lock warning - """ - self.course_outline_page.visit() - section = self.course_outline_page.section_at(0) - section.subsection_at(0).set_staff_lock(True) - section.subsection_at(1).set_staff_lock(True) - self.assertTrue(section.has_staff_lock_warning) - - def test_section_displays_lock_when_all_units_locked(self): - """ - Scenario: All units in a section are explicitly locked, section should display staff only warning - Given I have a course with one section, two subsections, and four units - When I enable explicit staff lock on all the units - Then the section shows a staff lock warning - """ - self.course_outline_page.visit() - self.course_outline_page.expand_all_subsections() - section = self.course_outline_page.section_at(0) - section.subsection_at(0).unit_at(0).set_staff_lock(True) - section.subsection_at(0).unit_at(1).set_staff_lock(True) - section.subsection_at(1).unit_at(0).set_staff_lock(True) - section.subsection_at(1).unit_at(1).set_staff_lock(True) - self.assertTrue(section.has_staff_lock_warning) - - def test_subsection_displays_lock_when_all_units_locked(self): - """ - Scenario: All units in subsection are explicitly locked, subsection should display staff only warning - Given I have a course with one subsection and two units - When I enable explicit staff lock on all the units - Then the subsection shows a staff lock warning - """ - self.course_outline_page.visit() - self.course_outline_page.expand_all_subsections() - subsection = self.course_outline_page.section_at(0).subsection_at(0) - subsection.unit_at(0).set_staff_lock(True) - subsection.unit_at(1).set_staff_lock(True) - self.assertTrue(subsection.has_staff_lock_warning) - - def test_section_does_not_display_lock_when_some_subsections_locked(self): - """ - Scenario: Only some subsections in section are explicitly locked, section should NOT display staff only warning - Given I have a course with one section and two subsections - When I enable explicit staff lock on one subsection - Then the section does not show a staff lock warning - """ - self.course_outline_page.visit() - section = self.course_outline_page.section_at(0) - section.subsection_at(0).set_staff_lock(True) - self.assertFalse(section.has_staff_lock_warning) - - def test_section_does_not_display_lock_when_some_units_locked(self): - """ - Scenario: Only some units in section are explicitly locked, section should NOT display staff only warning - Given I have a course with one section, two subsections, and four units - When I enable explicit staff lock on three units - Then the section does not show a staff lock warning - """ - self.course_outline_page.visit() - self.course_outline_page.expand_all_subsections() - section = self.course_outline_page.section_at(0) - section.subsection_at(0).unit_at(0).set_staff_lock(True) - section.subsection_at(0).unit_at(1).set_staff_lock(True) - section.subsection_at(1).unit_at(1).set_staff_lock(True) - self.assertFalse(section.has_staff_lock_warning) - - def test_subsection_does_not_display_lock_when_some_units_locked(self): - """ - Scenario: Only some units in subsection are explicitly locked, subsection should NOT display staff only warning - Given I have a course with one subsection and two units - When I enable explicit staff lock on one unit - Then the subsection does not show a staff lock warning - """ - self.course_outline_page.visit() - self.course_outline_page.expand_all_subsections() - subsection = self.course_outline_page.section_at(0).subsection_at(0) - subsection.unit_at(0).set_staff_lock(True) - self.assertFalse(subsection.has_staff_lock_warning) - - def test_locked_sections_do_not_appear_in_lms(self): - """ - Scenario: A locked section is not visible to students in the LMS - Given I have a course with two sections - When I enable explicit staff lock on one section - And I click the View Live button to switch to staff view - And I visit the course home with the outline - Then I see two sections in the outline - And when I switch the view mode to student view - Then I see one section in the outline - """ - self.course_outline_page.visit() - self.course_outline_page.add_section_from_top_button() - self.course_outline_page.section_at(1).set_staff_lock(True) - self.course_outline_page.view_live() - - course_home_page = CourseHomePage(self.browser, self.course_id) - course_home_page.visit() - course_home_page.wait_for_page() - self.assertEqual(course_home_page.outline.num_sections, 2) - course_home_page.preview.set_staff_view_mode('Learner') - course_home_page.wait_for(lambda: course_home_page.outline.num_sections == 1, - 'Only 1 section is visible in the outline') - - def test_toggling_staff_lock_on_section_does_not_publish_draft_units(self): - """ - Scenario: Locking and unlocking a section will not publish its draft units - Given I have a course with a section and unit - And the unit has a draft and published version - When I enable explicit staff lock on the section - And I disable explicit staff lock on the section - And I click the View Live button to switch to staff view - Then I see the published version of the unit - """ - self.course_outline_page.visit() - self.course_outline_page.expand_all_subsections() - unit = self.course_outline_page.section_at(0).subsection_at(0).unit_at(0).go_to() - add_discussion(unit) - self.course_outline_page.visit() - self.course_outline_page.expand_all_subsections() - section = self.course_outline_page.section_at(0) - section.set_staff_lock(True) - section.set_staff_lock(False) - unit = section.subsection_at(0).unit_at(0).go_to() - unit.view_published_version() - courseware = CoursewarePage(self.browser, self.course_id) - courseware.wait_for_page() - self.assertEqual(courseware.num_xblock_components, 0) - - def test_toggling_staff_lock_on_subsection_does_not_publish_draft_units(self): - """ - Scenario: Locking and unlocking a subsection will not publish its draft units - Given I have a course with a subsection and unit - And the unit has a draft and published version - When I enable explicit staff lock on the subsection - And I disable explicit staff lock on the subsection - And I click the View Live button to switch to staff view - Then I see the published version of the unit - """ - self.course_outline_page.visit() - self.course_outline_page.expand_all_subsections() - unit = self.course_outline_page.section_at(0).subsection_at(0).unit_at(0).go_to() - add_discussion(unit) - self.course_outline_page.visit() - self.course_outline_page.expand_all_subsections() - subsection = self.course_outline_page.section_at(0).subsection_at(0) - subsection.set_staff_lock(True) - subsection.set_staff_lock(False) - unit = subsection.unit_at(0).go_to() - unit.view_published_version() - courseware = CoursewarePage(self.browser, self.course_id) - courseware.wait_for_page() - self.assertEqual(courseware.num_xblock_components, 0) - - def test_removing_staff_lock_from_unit_without_inherited_lock_shows_warning(self): - """ - Scenario: Removing explicit staff lock from a unit which does not inherit staff lock displays a warning. - Given I have a course with a subsection and unit - When I enable explicit staff lock on the unit - And I disable explicit staff lock on the unit - Then I see a modal warning. - """ - self.course_outline_page.visit() - self.course_outline_page.expand_all_subsections() - unit = self.course_outline_page.section_at(0).subsection_at(0).unit_at(0) - unit.set_staff_lock(True) - self._remove_staff_lock_and_verify_warning(unit, True) - - def test_removing_staff_lock_from_subsection_without_inherited_lock_shows_warning(self): - """ - Scenario: Removing explicit staff lock from a subsection which does not inherit staff lock displays a warning. - Given I have a course with a section and subsection - When I enable explicit staff lock on the subsection - And I disable explicit staff lock on the subsection - Then I see a modal warning. - """ - self.course_outline_page.visit() - self.course_outline_page.expand_all_subsections() - subsection = self.course_outline_page.section_at(0).subsection_at(0) - subsection.set_staff_lock(True) - self._remove_staff_lock_and_verify_warning(subsection, True) - - def test_removing_staff_lock_from_unit_with_inherited_lock_shows_no_warning(self): - """ - Scenario: Removing explicit staff lock from a unit which also inherits staff lock displays no warning. - Given I have a course with a subsection and unit - When I enable explicit staff lock on the subsection - And I enable explicit staff lock on the unit - When I disable explicit staff lock on the unit - Then I do not see a modal warning. - """ - self.course_outline_page.visit() - self.course_outline_page.expand_all_subsections() - subsection = self.course_outline_page.section_at(0).subsection_at(0) - unit = subsection.unit_at(0) - subsection.set_staff_lock(True) - unit.set_staff_lock(True) - self._remove_staff_lock_and_verify_warning(unit, False) - - def test_removing_staff_lock_from_subsection_with_inherited_lock_shows_no_warning(self): - """ - Scenario: Removing explicit staff lock from a subsection which also inherits staff lock displays no warning. - Given I have a course with a section and subsection - When I enable explicit staff lock on the section - And I enable explicit staff lock on the subsection - When I disable explicit staff lock on the subsection - Then I do not see a modal warning. - """ - self.course_outline_page.visit() - self.course_outline_page.expand_all_subsections() - section = self.course_outline_page.section_at(0) - subsection = section.subsection_at(0) - section.set_staff_lock(True) - subsection.set_staff_lock(True) - self._remove_staff_lock_and_verify_warning(subsection, False) - - -@attr(shard=20) -class EditNamesTest(CourseOutlineTest): - """ - Feature: Click-to-edit section/subsection names - """ - - __test__ = True - - def set_name_and_verify(self, item, old_name, new_name, expected_name): - """ - Changes the display name of item from old_name to new_name, then verifies that its value is expected_name. - """ - self.assertEqual(item.name, old_name) - item.change_name(new_name) - self.assertFalse(item.in_editable_form()) - self.assertEqual(item.name, expected_name) - - def test_edit_section_name(self): - """ - Scenario: Click-to-edit section name - Given that I have created a section - When I click on the name of section - Then the section name becomes editable - And given that I have edited the section name - When I click outside of the edited section name - Then the section name saves - And becomes non-editable - """ - self.course_outline_page.visit() - self.set_name_and_verify( - self.course_outline_page.section_at(0), - 'Test Section', - 'Changed', - 'Changed' - ) - - def test_edit_subsection_name(self): - """ - Scenario: Click-to-edit subsection name - Given that I have created a subsection - When I click on the name of subsection - Then the subsection name becomes editable - And given that I have edited the subsection name - When I click outside of the edited subsection name - Then the subsection name saves - And becomes non-editable - """ - self.course_outline_page.visit() - self.set_name_and_verify( - self.course_outline_page.section_at(0).subsection_at(0), - 'Test Subsection', - 'Changed', - 'Changed' - ) - - def test_edit_empty_section_name(self): - """ - Scenario: Click-to-edit section name, enter empty name - Given that I have created a section - And I have clicked to edit the name of the section - And I have entered an empty section name - When I click outside of the edited section name - Then the section name does not change - And becomes non-editable - """ - self.course_outline_page.visit() - self.set_name_and_verify( - self.course_outline_page.section_at(0), - 'Test Section', - '', - 'Test Section' - ) - - def test_edit_empty_subsection_name(self): - """ - Scenario: Click-to-edit subsection name, enter empty name - Given that I have created a subsection - And I have clicked to edit the name of the subsection - And I have entered an empty subsection name - When I click outside of the edited subsection name - Then the subsection name does not change - And becomes non-editable - """ - self.course_outline_page.visit() - self.set_name_and_verify( - self.course_outline_page.section_at(0).subsection_at(0), - 'Test Subsection', - '', - 'Test Subsection' - ) - - def test_editing_names_does_not_expand_collapse(self): - """ - Scenario: A section stays in the same expand/collapse state while its name is edited - Given that I have created a section - And the section is collapsed - When I click on the name of the section - Then the section is collapsed - And given that I have entered a new name - Then the section is collapsed - And given that I press ENTER to finalize the name - Then the section is collapsed - """ - self.course_outline_page.visit() - self.course_outline_page.section_at(0).expand_subsection() - self.assertFalse(self.course_outline_page.section_at(0).in_editable_form()) - self.assertTrue(self.course_outline_page.section_at(0).is_collapsed) - self.course_outline_page.section_at(0).edit_name() - self.assertTrue(self.course_outline_page.section_at(0).in_editable_form()) - self.assertTrue(self.course_outline_page.section_at(0).is_collapsed) - self.course_outline_page.section_at(0).enter_name('Changed') - self.assertTrue(self.course_outline_page.section_at(0).is_collapsed) - self.course_outline_page.section_at(0).finalize_name() - self.assertTrue(self.course_outline_page.section_at(0).is_collapsed) - - -@attr(shard=14) -class CreateSectionsTest(CourseOutlineTest): - """ - Feature: Create new sections/subsections/units - """ - - __test__ = True - - def populate_course_fixture(self, course_fixture): - """ Start with a completely empty course to easily test adding things to it """ - pass - - def test_create_new_section_from_top_button(self): - """ - Scenario: Create new section from button at top of page - Given that I am on the course outline - When I click the "+ Add section" button at the top of the page - Then I see a new section added to the bottom of the page - And the display name is in its editable form. - """ - self.course_outline_page.visit() - self.course_outline_page.add_section_from_top_button() - self.assertEqual(len(self.course_outline_page.sections()), 1) - self.assertTrue(self.course_outline_page.section_at(0).in_editable_form()) - - def test_create_new_section_from_bottom_button(self): - """ - Scenario: Create new section from button at bottom of page - Given that I am on the course outline - When I click the "+ Add section" button at the bottom of the page - Then I see a new section added to the bottom of the page - And the display name is in its editable form. - """ - self.course_outline_page.visit() - self.course_outline_page.add_section_from_bottom_button() - self.assertEqual(len(self.course_outline_page.sections()), 1) - self.assertTrue(self.course_outline_page.section_at(0).in_editable_form()) - - def test_create_new_section_from_bottom_button_plus_icon(self): - """ - Scenario: Create new section from button plus icon at bottom of page - Given that I am on the course outline - When I click the plus icon in "+ Add section" button at the bottom of the page - Then I see a new section added to the bottom of the page - And the display name is in its editable form. - """ - self.course_outline_page.visit() - self.course_outline_page.add_section_from_bottom_button(click_child_icon=True) - self.assertEqual(len(self.course_outline_page.sections()), 1) - self.assertTrue(self.course_outline_page.section_at(0).in_editable_form()) - - def test_create_new_subsection(self): - """ - Scenario: Create new subsection - Given that I have created a section - When I click the "+ Add subsection" button in that section - Then I see a new subsection added to the bottom of the section - And the display name is in its editable form. - """ - self.course_outline_page.visit() - self.course_outline_page.add_section_from_top_button() - self.assertEqual(len(self.course_outline_page.sections()), 1) - self.course_outline_page.section_at(0).add_subsection() - subsections = self.course_outline_page.section_at(0).subsections() - self.assertEqual(len(subsections), 1) - self.assertTrue(subsections[0].in_editable_form()) - - def test_create_new_unit(self): - """ - Scenario: Create new unit - Given that I have created a section - And that I have created a subsection within that section - When I click the "+ Add unit" button in that subsection - Then I am redirected to a New Unit page - And the display name is in its editable form. - """ - self.course_outline_page.visit() - self.course_outline_page.add_section_from_top_button() - self.assertEqual(len(self.course_outline_page.sections()), 1) - self.course_outline_page.section_at(0).add_subsection() - self.assertEqual(len(self.course_outline_page.section_at(0).subsections()), 1) - self.course_outline_page.section_at(0).subsection_at(0).add_unit() - unit_page = ContainerPage(self.browser, None) - unit_page.wait_for_page() - self.assertTrue(unit_page.is_inline_editing_display_name()) - - -@attr(shard=4) -class DeleteContentTest(CourseOutlineTest): - """ - Feature: Deleting sections/subsections/units - """ - - __test__ = True - - def test_delete_section(self): - """ - Scenario: Delete section - Given that I am on the course outline - When I click the delete button for a section on the course outline - Then I should receive a confirmation message, asking me if I really want to delete the section - When I click "Yes, I want to delete this component" - Then the confirmation message should close - And the section should immediately be deleted from the course outline - """ - self.course_outline_page.visit() - self.assertEqual(len(self.course_outline_page.sections()), 1) - self.course_outline_page.section_at(0).delete() - self.assertEqual(len(self.course_outline_page.sections()), 0) - - def test_cancel_delete_section(self): - """ - Scenario: Cancel delete of section - Given that I clicked the delte button for a section on the course outline - And I received a confirmation message, asking me if I really want to delete the component - When I click "Cancel" - Then the confirmation message should close - And the section should remain in the course outline - """ - self.course_outline_page.visit() - self.assertEqual(len(self.course_outline_page.sections()), 1) - self.course_outline_page.section_at(0).delete(cancel=True) - self.assertEqual(len(self.course_outline_page.sections()), 1) - - def test_delete_subsection(self): - """ - Scenario: Delete subsection - Given that I am on the course outline - When I click the delete button for a subsection on the course outline - Then I should receive a confirmation message, asking me if I really want to delete the subsection - When I click "Yes, I want to delete this component" - Then the confiramtion message should close - And the subsection should immediately be deleted from the course outline - """ - self.course_outline_page.visit() - self.assertEqual(len(self.course_outline_page.section_at(0).subsections()), 1) - self.course_outline_page.section_at(0).subsection_at(0).delete() - self.assertEqual(len(self.course_outline_page.section_at(0).subsections()), 0) - - def test_cancel_delete_subsection(self): - """ - Scenario: Cancel delete of subsection - Given that I clicked the delete button for a subsection on the course outline - And I received a confirmation message, asking me if I really want to delete the subsection - When I click "cancel" - Then the confirmation message should close - And the subsection should remain in the course outline - """ - self.course_outline_page.visit() - self.assertEqual(len(self.course_outline_page.section_at(0).subsections()), 1) - self.course_outline_page.section_at(0).subsection_at(0).delete(cancel=True) - self.assertEqual(len(self.course_outline_page.section_at(0).subsections()), 1) - - def test_delete_unit(self): - """ - Scenario: Delete unit - Given that I am on the course outline - When I click the delete button for a unit on the course outline - Then I should receive a confirmation message, asking me if I really want to delete the unit - When I click "Yes, I want to delete this unit" - Then the confirmation message should close - And the unit should immediately be deleted from the course outline - """ - self.course_outline_page.visit() - self.course_outline_page.section_at(0).subsection_at(0).expand_subsection() - self.assertEqual(len(self.course_outline_page.section_at(0).subsection_at(0).units()), 1) - self.course_outline_page.section_at(0).subsection_at(0).unit_at(0).delete() - self.assertEqual(len(self.course_outline_page.section_at(0).subsection_at(0).units()), 0) - - def test_cancel_delete_unit(self): - """ - Scenario: Cancel delete of unit - Given that I clicked the delete button for a unit on the course outline - And I received a confirmation message, asking me if I really want to delete the unit - When I click "Cancel" - Then the confirmation message should close - And the unit should remain in the course outline - """ - self.course_outline_page.visit() - self.course_outline_page.section_at(0).subsection_at(0).expand_subsection() - self.assertEqual(len(self.course_outline_page.section_at(0).subsection_at(0).units()), 1) - self.course_outline_page.section_at(0).subsection_at(0).unit_at(0).delete(cancel=True) - self.assertEqual(len(self.course_outline_page.section_at(0).subsection_at(0).units()), 1) - - def test_delete_all_no_content_message(self): - """ - Scenario: Delete all sections/subsections/units in a course, "no content" message should appear - Given that I delete all sections, subsections, and units in a course - When I visit the course outline - Then I will see a message that says, "You haven't added any content to this course yet" - Add see a + Add Section button - """ - self.course_outline_page.visit() - self.assertFalse(self.course_outline_page.has_no_content_message) - self.course_outline_page.section_at(0).delete() - self.assertEqual(len(self.course_outline_page.sections()), 0) - self.assertTrue(self.course_outline_page.has_no_content_message) - - -@attr(shard=14) -class ExpandCollapseMultipleSectionsTest(CourseOutlineTest): - """ - Feature: Courses with multiple sections can expand and collapse all sections. - """ - - __test__ = True - - def populate_course_fixture(self, course_fixture): - """ Start with a course with two sections """ - course_fixture.add_children( - XBlockFixtureDesc('chapter', 'Test Section').add_children( - XBlockFixtureDesc('sequential', 'Test Subsection').add_children( - XBlockFixtureDesc('vertical', 'Test Unit') - ) - ), - XBlockFixtureDesc('chapter', 'Test Section 2').add_children( - XBlockFixtureDesc('sequential', 'Test Subsection 2').add_children( - XBlockFixtureDesc('vertical', 'Test Unit 2') - ) - ) - ) - - def verify_all_sections(self, collapsed): - """ - Verifies that all sections are collapsed if collapsed is True, otherwise all expanded. - """ - for section in self.course_outline_page.sections(): - self.assertEqual(collapsed, section.is_collapsed) - - def toggle_all_sections(self): - """ - Toggles the expand collapse state of all sections. - """ - for section in self.course_outline_page.sections(): - section.expand_subsection() - - def test_expanded_by_default(self): - """ - Scenario: The default layout for the outline page is to show sections in expanded view - Given I have a course with sections - When I navigate to the course outline page - Then I see the "Collapse All Sections" link - And all sections are expanded - """ - self.course_outline_page.visit() - self.assertEqual(self.course_outline_page.expand_collapse_link_state, ExpandCollapseLinkState.COLLAPSE) - self.verify_all_sections(collapsed=False) - - def test_no_expand_link_for_empty_course(self): - """ - Scenario: Collapse link is removed after last section of a course is deleted - Given I have a course with multiple sections - And I navigate to the course outline page - When I will confirm all alerts - And I press the "section" delete icon - Then I do not see the "Collapse All Sections" link - And I will see a message that says "You haven't added any content to this course yet" - """ - self.course_outline_page.visit() - for section in self.course_outline_page.sections(): - section.delete() - self.assertEqual(self.course_outline_page.expand_collapse_link_state, ExpandCollapseLinkState.MISSING) - self.assertTrue(self.course_outline_page.has_no_content_message) - - def test_collapse_all_when_all_expanded(self): - """ - Scenario: Collapse all sections when all sections are expanded - Given I navigate to the outline page of a course with sections - And all sections are expanded - When I click the "Collapse All Sections" link - Then I see the "Expand All Sections" link - And all sections are collapsed - """ - self.course_outline_page.visit() - self.verify_all_sections(collapsed=False) - self.course_outline_page.toggle_expand_collapse() - self.assertEqual(self.course_outline_page.expand_collapse_link_state, ExpandCollapseLinkState.EXPAND) - self.verify_all_sections(collapsed=True) - - def test_collapse_all_when_some_expanded(self): - """ - Scenario: Collapsing all sections when 1 or more sections are already collapsed - Given I navigate to the outline page of a course with sections - And all sections are expanded - When I collapse the first section - And I click the "Collapse All Sections" link - Then I see the "Expand All Sections" link - And all sections are collapsed - """ - self.course_outline_page.visit() - self.verify_all_sections(collapsed=False) - self.course_outline_page.section_at(0).expand_subsection() - self.course_outline_page.toggle_expand_collapse() - self.assertEqual(self.course_outline_page.expand_collapse_link_state, ExpandCollapseLinkState.EXPAND) - self.verify_all_sections(collapsed=True) - - def test_expand_all_when_all_collapsed(self): - """ - Scenario: Expanding all sections when all sections are collapsed - Given I navigate to the outline page of a course with multiple sections - And I click the "Collapse All Sections" link - When I click the "Expand All Sections" link - Then I see the "Collapse All Sections" link - And all sections are expanded - """ - self.course_outline_page.visit() - self.course_outline_page.toggle_expand_collapse() - self.assertEqual(self.course_outline_page.expand_collapse_link_state, ExpandCollapseLinkState.EXPAND) - self.course_outline_page.toggle_expand_collapse() - self.assertEqual(self.course_outline_page.expand_collapse_link_state, ExpandCollapseLinkState.COLLAPSE) - self.verify_all_sections(collapsed=False) - - def test_expand_all_when_some_collapsed(self): - """ - Scenario: Expanding all sections when 1 or more sections are already expanded - Given I navigate to the outline page of a course with multiple sections - And I click the "Collapse All Sections" link - 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 - """ - self.course_outline_page.visit() - # We have seen unexplainable sporadic failures in this test. Try disabling animations to see - # if that helps. - disable_animations(self.course_outline_page) - self.course_outline_page.toggle_expand_collapse() - self.assertEqual(self.course_outline_page.expand_collapse_link_state, ExpandCollapseLinkState.EXPAND) - self.verify_all_sections(collapsed=True) - self.course_outline_page.section_at(0).expand_subsection() - self.course_outline_page.toggle_expand_collapse() - self.assertEqual(self.course_outline_page.expand_collapse_link_state, ExpandCollapseLinkState.COLLAPSE) - self.verify_all_sections(collapsed=False) - - -@attr(shard=14) -class ExpandCollapseSingleSectionTest(CourseOutlineTest): - """ - Feature: Courses with a single section can expand and collapse all sections. - """ - - __test__ = True - - def test_no_expand_link_for_empty_course(self): - """ - Scenario: Collapse link is removed after last section of a course is deleted - Given I have a course with one section - And I navigate to the course outline page - When I will confirm all alerts - And I press the "section" delete icon - Then I do not see the "Collapse All Sections" link - And I will see a message that says "You haven't added any content to this course yet" - """ - self.course_outline_page.visit() - self.course_outline_page.section_at(0).delete() - self.assertEqual(self.course_outline_page.expand_collapse_link_state, ExpandCollapseLinkState.MISSING) - self.assertTrue(self.course_outline_page.has_no_content_message) - - def test_old_subsection_stays_collapsed_after_creation(self): - """ - Scenario: Collapsed subsection stays collapsed after creating a new subsection - Given I have a course with one section and subsection - And I navigate to the course outline page - Then the subsection is collapsed - And when I create a new subsection - Then the first subsection is collapsed - And the second subsection is expanded - """ - self.course_outline_page.visit() - self.assertTrue(self.course_outline_page.section_at(0).subsection_at(0).is_collapsed) - self.course_outline_page.section_at(0).add_subsection() - self.assertTrue(self.course_outline_page.section_at(0).subsection_at(0).is_collapsed) - self.assertFalse(self.course_outline_page.section_at(0).subsection_at(1).is_collapsed) - - -@attr(shard=14) -class ExpandCollapseEmptyTest(CourseOutlineTest): - """ - Feature: Courses with no sections initially can expand and collapse all sections after addition. - """ - - __test__ = True - - def populate_course_fixture(self, course_fixture): - """ Start with an empty course """ - pass - - def test_no_expand_link_for_empty_course(self): - """ - Scenario: Expand/collapse for a course with no sections - Given I have a course with no sections - When I navigate to the course outline page - Then I do not see the "Collapse All Sections" link - """ - self.course_outline_page.visit() - self.assertEqual(self.course_outline_page.expand_collapse_link_state, ExpandCollapseLinkState.MISSING) - - def test_link_appears_after_section_creation(self): - """ - 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 outline page - And I add a section - Then I see the "Collapse All Sections" link - And all sections are expanded - """ - self.course_outline_page.visit() - self.assertEqual(self.course_outline_page.expand_collapse_link_state, ExpandCollapseLinkState.MISSING) - self.course_outline_page.add_section_from_top_button() - self.assertEqual(self.course_outline_page.expand_collapse_link_state, ExpandCollapseLinkState.COLLAPSE) - self.assertFalse(self.course_outline_page.section_at(0).is_collapsed) - - -@attr(shard=20) -class DefaultStatesEmptyTest(CourseOutlineTest): - """ - Feature: Misc course outline default states/actions when starting with an empty course - """ - - __test__ = True - - def populate_course_fixture(self, course_fixture): - """ Start with an empty course """ - pass - - def test_empty_course_message(self): - """ - Scenario: Empty course state - Given that I am in a course with no sections, subsections, nor units - When I visit the course outline - Then I will see a message that says "You haven't added any content to this course yet" - And see a + Add Section button - """ - self.course_outline_page.visit() - self.assertTrue(self.course_outline_page.has_no_content_message) - self.assertTrue(self.course_outline_page.bottom_add_section_button.is_present()) - - -@attr(shard=4) -class DefaultStatesContentTest(CourseOutlineTest): - """ - Feature: Misc course outline default states/actions when starting with a course with content - """ - - __test__ = True - - def test_view_live(self): - """ - Scenario: View Live version from course outline - Given that I am on the course outline - When I click the "View Live" button - Then a new tab will open to the course on the LMS - """ - self.course_outline_page.visit() - self.course_outline_page.view_live() - courseware = CoursewarePage(self.browser, self.course_id) - courseware.wait_for_page() - self.assertEqual(courseware.num_xblock_components, 3) - self.assertEqual(courseware.xblock_component_type(0), 'problem') - self.assertEqual(courseware.xblock_component_type(1), 'html') - self.assertEqual(courseware.xblock_component_type(2), 'discussion') - - -@attr(shard=7) -class UnitNavigationTest(CourseOutlineTest): - """ - Feature: Navigate to units - """ - - __test__ = True - - def test_navigate_to_unit(self): - """ - Scenario: Click unit name to navigate to unit page - Given that I have expanded a section/subsection so I can see unit names - When I click on a unit name - Then I will be taken to the appropriate unit page - """ - self.course_outline_page.visit() - self.course_outline_page.section_at(0).subsection_at(0).expand_subsection() - unit = self.course_outline_page.section_at(0).subsection_at(0).unit_at(0).go_to() - unit.wait_for_page() - - -@attr(shard=7) -class PublishSectionTest(CourseOutlineTest): - """ - Feature: Publish sections. - """ - - __test__ = True - - def populate_course_fixture(self, course_fixture): - """ - Sets up a course structure with 2 subsections inside a single section. - The first subsection has 2 units, and the second subsection has one unit. - """ - self.courseware = CoursewarePage(self.browser, self.course_id) - self.course_home_page = CourseHomePage(self.browser, self.course_id) - course_fixture.add_children( - XBlockFixtureDesc('chapter', SECTION_NAME).add_children( - XBlockFixtureDesc('sequential', SUBSECTION_NAME).add_children( - XBlockFixtureDesc('vertical', UNIT_NAME), - XBlockFixtureDesc('vertical', 'Test Unit 2'), - ), - XBlockFixtureDesc('sequential', 'Test Subsection 2').add_children( - XBlockFixtureDesc('vertical', 'Test Unit 3'), - ), - ), - ) - - def test_unit_publishing(self): - """ - Scenario: Can publish a unit and see published content in LMS - Given I have a section with 2 subsections and 3 unpublished units - When I go to the course outline - Then I see publish button for the first unit, subsection, section - When I publish the first unit - Then I see that publish button for the first unit disappears - And I see publish buttons for subsection, section - And I see the changed content in LMS - """ - self._add_unpublished_content() - self.course_outline_page.visit() - section, subsection, unit = self._get_items() - self.assertTrue(unit.publish_action) - self.assertTrue(subsection.publish_action) - self.assertTrue(section.publish_action) - unit.publish() - self.assertFalse(unit.publish_action) - self.assertTrue(subsection.publish_action) - self.assertTrue(section.publish_action) - self.courseware.visit() - self.assertEqual(1, self.courseware.num_xblock_components) - - def test_subsection_publishing(self): - """ - Scenario: Can publish a subsection and see published content in LMS - Given I have a section with 2 subsections and 3 unpublished units - When I go to the course outline - Then I see publish button for the unit, subsection, section - When I publish the first subsection - Then I see that publish button for the first subsection disappears - And I see that publish buttons disappear for the child units of the subsection - And I see publish button for section - And I see the changed content in LMS - """ - self._add_unpublished_content() - self.course_outline_page.visit() - section, subsection, unit = self._get_items() - self.assertTrue(unit.publish_action) - self.assertTrue(subsection.publish_action) - self.assertTrue(section.publish_action) - self.course_outline_page.section(SECTION_NAME).subsection(SUBSECTION_NAME).publish() - self.assertFalse(unit.publish_action) - self.assertFalse(subsection.publish_action) - self.assertTrue(section.publish_action) - self.courseware.visit() - self.assertEqual(1, self.courseware.num_xblock_components) - self.courseware.go_to_sequential_position(2) - self.assertEqual(1, self.courseware.num_xblock_components) - - def test_section_publishing(self): - """ - Scenario: Can publish a section and see published content in LMS - Given I have a section with 2 subsections and 3 unpublished units - When I go to the course outline - Then I see publish button for the unit, subsection, section - When I publish the section - Then I see that publish buttons disappears - And I see the changed content in LMS - """ - self._add_unpublished_content() - self.course_outline_page.visit() - section, subsection, unit = self._get_items() - self.assertTrue(subsection.publish_action) - self.assertTrue(section.publish_action) - self.assertTrue(unit.publish_action) - self.course_outline_page.section(SECTION_NAME).publish() - self.assertFalse(subsection.publish_action) - self.assertFalse(section.publish_action) - self.assertFalse(unit.publish_action) - self.courseware.visit() - self.assertEqual(1, self.courseware.num_xblock_components) - self.courseware.go_to_sequential_position(2) - self.assertEqual(1, self.courseware.num_xblock_components) - self.course_home_page.visit() - self.course_home_page.outline.go_to_section(SECTION_NAME, 'Test Subsection 2') - self.assertEqual(1, self.courseware.num_xblock_components) - - def _add_unpublished_content(self): - """ - Adds unpublished HTML content to first three units in the course. - """ - for index in range(3): - self.course_fixture.create_xblock( - self.course_fixture.get_nested_xblocks(category="vertical")[index].locator, - XBlockFixtureDesc('html', 'Unpublished HTML Component ' + str(index)), - ) - - def _get_items(self): - """ - Returns first section, subsection, and unit on the page. - """ - section = self.course_outline_page.section(SECTION_NAME) - subsection = section.subsection(SUBSECTION_NAME) - unit = subsection.expand_subsection().unit(UNIT_NAME) - - return (section, subsection, unit) - - -@attr(shard=7) -class DeprecationWarningMessageTest(CourseOutlineTest): - """ - Feature: Verify deprecation warning message. - """ - HEADING_TEXT = 'This course uses features that are no longer supported.' - COMPONENT_LIST_HEADING = 'You must delete or replace the following components.' - ADVANCE_MODULES_REMOVE_TEXT = ( - u'To avoid errors, édX strongly recommends that you remove unsupported features ' - u'from the course advanced settings. To do this, go to the Advanced Settings ' - u'page, locate the "Advanced Module List" setting, and then delete the following ' - u'modules from the list.' - ) - DEFAULT_DISPLAYNAME = "Deprecated Component" - - def _add_deprecated_advance_modules(self, block_types): - """ - Add `block_types` into `Advanced Module List` - - Arguments: - block_types (list): list of block types - """ - self.advanced_settings.visit() - self.advanced_settings.set_values({"Advanced Module List": json.dumps(block_types)}) - - def _create_deprecated_components(self): - """ - Create deprecated components. - """ - parent_vertical = self.course_fixture.get_nested_xblocks(category="vertical")[0] - - self.course_fixture.create_xblock( - parent_vertical.locator, - XBlockFixtureDesc('poll', "Poll", data=load_data_str('poll_markdown.xml')) - ) - self.course_fixture.create_xblock(parent_vertical.locator, XBlockFixtureDesc('survey', 'Survey')) - - def _verify_deprecation_warning_info( - self, - deprecated_blocks_present, - components_present, - components_display_name_list=None, - deprecated_modules_list=None - ): - """ - Verify deprecation warning - - Arguments: - deprecated_blocks_present (bool): deprecated blocks remove text and - is list is visible if True else False - components_present (bool): components list shown if True else False - components_display_name_list (list): list of components display name - deprecated_modules_list (list): list of deprecated advance modules - """ - self.assertTrue(self.course_outline_page.deprecated_warning_visible) - self.assertEqual(self.course_outline_page.warning_heading_text, self.HEADING_TEXT) - self.assertEqual(self.course_outline_page.modules_remove_text_shown, deprecated_blocks_present) - if deprecated_blocks_present: - self.assertEqual(self.course_outline_page.modules_remove_text, self.ADVANCE_MODULES_REMOVE_TEXT) - self.assertEqual(self.course_outline_page.deprecated_advance_modules, deprecated_modules_list) - - self.assertEqual(self.course_outline_page.components_visible, components_present) - if components_present: - self.assertEqual(self.course_outline_page.components_list_heading, self.COMPONENT_LIST_HEADING) - six.assertCountEqual(self, self.course_outline_page.components_display_names, components_display_name_list) - - def test_no_deprecation_warning_message_present(self): - """ - Scenario: Verify that deprecation warning message is not shown if no deprecated - advance modules are not present and also no deprecated component exist in - course outline. - - When I goto course outline - Then I don't see any deprecation warning - """ - self.course_outline_page.visit() - self.assertFalse(self.course_outline_page.deprecated_warning_visible) - - def test_deprecation_warning_message_present(self): - """ - Scenario: Verify deprecation warning message if deprecated modules - and components are present. - - Given I have "poll" advance modules present in `Advanced Module List` - And I have created 2 poll components - When I go to course outline - Then I see poll deprecated warning - And I see correct poll deprecated warning heading text - And I see correct poll deprecated warning advance modules remove text - And I see list of poll components with correct display names - """ - self._add_deprecated_advance_modules(block_types=['poll', 'survey']) - self._create_deprecated_components() - self.course_outline_page.visit() - self._verify_deprecation_warning_info( - deprecated_blocks_present=True, - components_present=True, - components_display_name_list=['Poll', 'Survey'], - deprecated_modules_list=['poll', 'survey'] - ) - - def test_deprecation_warning_with_no_displayname(self): - """ - Scenario: Verify deprecation warning message if poll components are present. - - Given I have created 1 poll deprecated component - When I go to course outline - Then I see poll deprecated warning - And I see correct poll deprecated warning heading text - And I see list of poll components with correct message - """ - parent_vertical = self.course_fixture.get_nested_xblocks(category="vertical")[0] - - # Create a deprecated component with display_name to be empty and make sure - # the deprecation warning is displayed with - self.course_fixture.create_xblock( - parent_vertical.locator, - XBlockFixtureDesc(category='poll', display_name="", data=load_data_str('poll_markdown.xml')) - ) - self.course_outline_page.visit() - - self._verify_deprecation_warning_info( - deprecated_blocks_present=False, - components_present=True, - components_display_name_list=[self.DEFAULT_DISPLAYNAME], - ) - - def test_warning_with_poll_advance_modules_only(self): - """ - Scenario: Verify that deprecation warning message is shown if only - poll advance modules are present and no poll component exist. - - Given I have poll advance modules present in `Advanced Module List` - When I go to course outline - Then I see poll deprecated warning - And I see correct poll deprecated warning heading text - And I see correct poll deprecated warning advance modules remove text - And I don't see list of poll components - """ - self._add_deprecated_advance_modules(block_types=['poll', 'survey']) - self.course_outline_page.visit() - self._verify_deprecation_warning_info( - deprecated_blocks_present=True, - components_present=False, - deprecated_modules_list=['poll', 'survey'] - ) - - def test_warning_with_poll_components_only(self): - """ - Scenario: Verify that deprecation warning message is shown if only - poll component exist and no poll advance modules are present. - - Given I have created two poll components - When I go to course outline - Then I see poll deprecated warning - And I see correct poll deprecated warning heading text - And I don't see poll deprecated warning advance modules remove text - And I see list of poll components with correct display names - """ - self._create_deprecated_components() - self.course_outline_page.visit() - self._verify_deprecation_warning_info( - deprecated_blocks_present=False, - components_present=True, - components_display_name_list=['Poll', 'Survey'] - ) - - -@attr(shard=4) -class SelfPacedOutlineTest(CourseOutlineTest): - """Test the course outline for a self-paced course.""" - - def populate_course_fixture(self, course_fixture): - course_fixture.add_children( - XBlockFixtureDesc('chapter', SECTION_NAME).add_children( - XBlockFixtureDesc('sequential', SUBSECTION_NAME).add_children( - XBlockFixtureDesc('vertical', UNIT_NAME) - ) - ), - ) - self.course_fixture.add_course_details({ - 'self_paced': True, - 'start_date': datetime.now() + timedelta(days=1) - }) - ConfigModelFixture('/config/self_paced', {'enabled': True}).install() - - def test_release_dates_not_shown(self): - """ - Scenario: Ensure that block release dates are not shown on the - course outline page of a self-paced course. - - Given I am the author of a self-paced course - When I go to the course outline - Then I should not see release dates for course content - """ - self.course_outline_page.visit() - section = self.course_outline_page.section(SECTION_NAME) - self.assertEqual(section.release_date, '') - subsection = section.subsection(SUBSECTION_NAME) - self.assertEqual(subsection.release_date, '') - - def test_edit_section_and_subsection(self): - """ - Scenario: Ensure that block release/due dates are not shown - in their settings modals. - - Given I am the author of a self-paced course - When I go to the course outline - And I click on settings for a section or subsection - Then I should not see release or due date settings - """ - self.course_outline_page.visit() - section = self.course_outline_page.section(SECTION_NAME) - modal = section.edit() - self.assertFalse(modal.has_release_date()) - self.assertFalse(modal.has_due_date()) - modal.cancel() - subsection = section.subsection(SUBSECTION_NAME) - modal = subsection.edit() - self.assertFalse(modal.has_release_date()) - self.assertFalse(modal.has_due_date()) - - -class CourseStatusOutlineTest(CourseOutlineTest): - """Test the course outline status section.""" - shard = 8 - - def setUp(self): - super(CourseStatusOutlineTest, self).setUp() - - self.schedule_and_details_settings = SettingsPage( - self.browser, - self.course_info['org'], - self.course_info['number'], - self.course_info['run'] - ) - - self.checklists = CourseChecklistsPage( - self.browser, - self.course_info['org'], - self.course_info['number'], - self.course_info['run'] - ) - - def test_course_status_section(self): - """ - Ensure that the course status section appears in the course outline. - """ - self.course_outline_page.visit() - self.assertTrue(self.course_outline_page.has_course_status_section) - - def test_course_status_section_start_date_link(self): - """ - Ensure that the course start date link in the course status section in - the course outline links to the "Schedule and Details" page. - """ - self.course_outline_page.visit() - self.course_outline_page.click_course_status_section_start_date_link() - self.schedule_and_details_settings.wait_for_page() - - def test_course_status_section_checklists_link(self): - """ - Ensure that the course checklists link in the course status section in - the course outline links to the "Checklists" page. - """ - self.course_outline_page.visit() - self.course_outline_page.click_course_status_section_checklists_link() - self.checklists.wait_for_page() - - -class InstructorPacedToSelfPacedOutlineTest(CourseOutlineTest): - """ - Test the course outline when pacing is changed from - instructor to self paced. - """ - def populate_course_fixture(self, course_fixture): - course_fixture.add_children( - XBlockFixtureDesc('chapter', SECTION_NAME).add_children( - XBlockFixtureDesc('sequential', SUBSECTION_NAME).add_children( - XBlockFixtureDesc('vertical', UNIT_NAME) - ) - ), - ) - self.course_fixture.add_course_details({ - 'start_date': datetime.now() + timedelta(days=1), - }) - self.course_fixture.add_advanced_settings({ - 'enable_timed_exams': { - 'value': True - } - }) - - def test_due_dates_not_shown(self): - """ - Scenario: Ensure that due dates for timed exams - are not displayed on the course outline page when switched to - self-paced mode from instructor-paced. - - Given an instructor paced course, add a due date for a subsection. - Change the course's pacing to self-paced. - Make the subsection a timed exam. - Make sure adding the timed exam doesn't display the due date. - """ - self.course_outline_page.visit() - section = self.course_outline_page.section(SECTION_NAME) - subsection = section.subsection(SUBSECTION_NAME) - - modal = subsection.edit() - modal.due_date = '5/14/2016' - modal.policy = 'Homework' - modal.save() - # Checking if the added due date saved - self.assertIn('May 14', subsection.due_date) - # Checking if grading policy added - self.assertEqual('Homework', subsection.policy) - - # Updating the course mode to self-paced - self.course_fixture.add_course_details({ - 'self_paced': True - }) - # Making the subsection a timed exam - self.course_outline_page.open_subsection_settings_dialog() - self.course_outline_page.select_advanced_tab() - self.course_outline_page.make_exam_timed() - - # configure call to actually update course with new settings - self.course_fixture.configure_course() - # Reloading page after the changes - self.course_outline_page.visit() - self.assertIsNone(subsection.due_date) diff --git a/common/test/acceptance/tests/studio/test_studio_problem_editor.py b/common/test/acceptance/tests/studio/test_studio_problem_editor.py deleted file mode 100644 index 4b0b036efe..0000000000 --- a/common/test/acceptance/tests/studio/test_studio_problem_editor.py +++ /dev/null @@ -1,123 +0,0 @@ -""" -Acceptance tests for Problem component in studio -""" - - -from common.test.acceptance.fixtures.course import XBlockFixtureDesc -from common.test.acceptance.pages.studio.container import ContainerPage -from common.test.acceptance.pages.studio.problem_editor import ProblemXBlockEditorView -from common.test.acceptance.pages.studio.utils import add_component -from common.test.acceptance.tests.helpers import skip_if_browser -from common.test.acceptance.tests.studio.base_studio_test import ContainerBase - - -class ProblemComponentEditor(ContainerBase): - """ - Feature: CMS.Component Adding - As a course author, I want to be able to add and edit Problem - """ - - def setUp(self, is_staff=True): - """ - Create a course with a section, subsection, and unit to which to add the component. - """ - super(ProblemComponentEditor, self).setUp(is_staff=is_staff) - self.component = 'Blank Common Problem' - self.unit = self.go_to_unit_page() - self.container_page = ContainerPage(self.browser, None) - # Add a Problem - add_component(self.container_page, 'problem', self.component) - self.component = self.unit.xblocks[1] - self.container_page.edit() - self.problem_editor = ProblemXBlockEditorView(self.browser, self.component.locator) - - def populate_course_fixture(self, course_fixture): - """ - Adds a course fixture - """ - course_fixture.add_children( - XBlockFixtureDesc('chapter', 'Test Section').add_children( - XBlockFixtureDesc('sequential', 'Test Subsection').add_children( - XBlockFixtureDesc('vertical', 'Test Unit') - ) - ) - ) - - def test_user_can_modify_float_input(self): - """ - Scenario: User can modify float input values - Given I have created a Blank Common Problem - When I edit and select Settings - Then I can set the weight to "3.5" - And my change to weight is persisted - And I can revert to the default value of unset for weight - """ - self.problem_editor.open_settings() - self.problem_editor.set_field_val('Problem Weight', '3.5') - self.problem_editor.save() - - # reopen settings - self.container_page.edit() - self.problem_editor.open_settings() - - field_value = self.problem_editor.get_field_val('Problem Weight') - self.assertEqual(field_value, '3.5') - self.problem_editor.revert_setting() - field_value = self.problem_editor.get_field_val('Problem Weight') - self.assertEqual(field_value, '', 'Component settings is not reverted to default') - - @skip_if_browser('firefox') - # Lettuce tests run on chrome and chrome does not allow to enter - # periods/dots in this field and consequently we have to save the - # value as '234'. Whereas, bokchoy runs with the older version of - # firefox on jenkins, which does not allow to save the value if it - # has a period/dot. Clicking on save button after filling '2.34' in - # field, does not do anything and test does not go any further. - # So, it fails always. - def test_user_cannot_type_decimal_values(self): - """ - Scenario: User cannot type decimal values integer number field - Given I have created a Blank Common Problem - When I edit and select Settings - Then if I set the max attempts to "2.34", it will persist as a valid integer - """ - self.problem_editor.open_settings() - self.problem_editor.set_field_val('Maximum Attempts', '2.34') - self.problem_editor.save() - - # reopen settings - self.container_page.edit() - self.problem_editor.open_settings() - - field_value = self.problem_editor.get_field_val('Maximum Attempts') - self.assertEqual(field_value, '234', "Decimal values are not allowed in this field") - - def test_settings_are_not_saved_on_cancel(self): - """ - Scenario: Settings changes are not saved on Cancel - Given I have created a Blank Common Problem - When I edit and select Settings - Then I can set the weight to "3.5" - And I can modify the display name - Then If I press Cancel my changes are not persisted - """ - self.problem_editor.open_settings() - self.problem_editor.set_field_val('Problem Weight', '3.5') - self.problem_editor.cancel() - - # reopen settings - self.container_page.edit() - self.problem_editor.open_settings() - - field_value = self.problem_editor.get_field_val('Problem Weight') - self.assertEqual(field_value, '', "Component setting should not appear updated if cancelled during editing") - - def test_cheat_sheet_visible_on_toggle(self): - """ - Scenario: Cheat sheet visible on toggle - Given I have created a Blank Common Problem - And I can edit the problem - Then I can see cheatsheet - """ - self.problem_editor.toggle_cheatsheet() - self.assertTrue(self.problem_editor.is_cheatsheet_present(), "Cheatsheet not present") diff --git a/common/test/acceptance/tests/studio/test_studio_settings.py b/common/test/acceptance/tests/studio/test_studio_settings.py index d28aae2ea8..03ce60acf3 100644 --- a/common/test/acceptance/tests/studio/test_studio_settings.py +++ b/common/test/acceptance/tests/studio/test_studio_settings.py @@ -5,230 +5,13 @@ Acceptance tests for Studio's Setting pages import os -from textwrap import dedent - -from bok_choy.promise import EmptyPromise from mock import patch from common.test.acceptance.fixtures.course import XBlockFixtureDesc -from common.test.acceptance.pages.common.utils import add_enrollment_course_modes from common.test.acceptance.pages.studio.overview import CourseOutlinePage from common.test.acceptance.pages.studio.settings import SettingsPage -from common.test.acceptance.pages.studio.settings_group_configurations import GroupConfigurationsPage -from common.test.acceptance.pages.studio.utils import get_input_value -from common.test.acceptance.tests.helpers import create_user_partition_json, element_has_text from common.test.acceptance.tests.studio.base_studio_test import StudioCourseTest from openedx.core.lib.tests import attr -from xmodule.partitions.partitions import Group - - -@attr(shard=19) -class ContentGroupConfigurationTest(StudioCourseTest): - """ - Tests for content groups in the Group Configurations Page. - There are tests for the experiment groups in test_studio_split_test. - """ - def setUp(self): - super(ContentGroupConfigurationTest, self).setUp() - self.group_configurations_page = GroupConfigurationsPage( - self.browser, - self.course_info['org'], - self.course_info['number'], - self.course_info['run'] - ) - - self.outline_page = CourseOutlinePage( - self.browser, - self.course_info['org'], - self.course_info['number'], - self.course_info['run'] - ) - - def populate_course_fixture(self, course_fixture): - """ - Populates test course with chapter, sequential, and 1 problems. - The problem is visible only to Group "alpha". - """ - course_fixture.add_children( - XBlockFixtureDesc('chapter', 'Test Section').add_children( - XBlockFixtureDesc('sequential', 'Test Subsection').add_children( - XBlockFixtureDesc('vertical', 'Test Unit') - ) - ) - ) - - def create_and_verify_content_group(self, name, existing_groups): - """ - Creates a new content group and verifies that it was properly created. - """ - self.assertEqual(existing_groups, len(self.group_configurations_page.content_groups)) - if existing_groups == 0: - self.group_configurations_page.create_first_content_group() - else: - self.group_configurations_page.add_content_group() - config = self.group_configurations_page.content_groups[existing_groups] - config.name = name - # Save the content group - self.assertEqual(config.get_text('.action-primary'), "Create") - self.assertFalse(config.delete_button_is_present) - config.save() - self.assertIn(name, config.name) - return config - - def test_no_content_groups_by_default(self): - """ - Scenario: Ensure that message telling me to create a new content group is - shown when no content groups exist. - Given I have a course without content groups - When I go to the Group Configuration page in Studio - Then I see "You have not created any content groups yet." message - """ - self.group_configurations_page.visit() - self.assertTrue(self.group_configurations_page.no_content_groups_message_is_present) - self.assertIn( - "You have not created any content groups yet.", - self.group_configurations_page.no_content_groups_message_text - ) - - def test_can_create_and_edit_content_groups(self): - """ - Scenario: Ensure that the content groups can be created and edited correctly. - Given I have a course without content groups - When I click button 'Add your first Content Group' - And I set new the name and click the button 'Create' - Then I see the new content is added and has correct data - And I click 'New Content Group' button - And I set the name and click the button 'Create' - Then I see the second content group is added and has correct data - When I edit the second content group - And I change the name and click the button 'Save' - Then I see the second content group is saved successfully and has the new name - """ - self.group_configurations_page.visit() - self.create_and_verify_content_group("New Content Group", 0) - second_config = self.create_and_verify_content_group("Second Content Group", 1) - - # Edit the second content group - second_config.edit() - second_config.name = "Updated Second Content Group" - self.assertEqual(second_config.get_text('.action-primary'), "Save") - second_config.save() - - self.assertIn("Updated Second Content Group", second_config.name) - - def test_cannot_delete_used_content_group(self): - """ - Scenario: Ensure that the user cannot delete used content group. - Given I have a course with 1 Content Group - And I go to the Group Configuration page - When I try to delete the Content Group with name "New Content Group" - Then I see the delete button is disabled. - """ - self.course_fixture._update_xblock(self.course_fixture._course_location, { - "metadata": { - u"user_partitions": [ - create_user_partition_json( - 0, - 'Configuration alpha,', - 'Content Group Partition', - [Group("0", 'alpha')], - scheme="cohort" - ) - ], - }, - }) - problem_data = dedent(""" -Dropdown
- -- - """) - vertical = self.course_fixture.get_nested_xblocks(category="vertical")[0] - self.course_fixture.create_xblock( - vertical.locator, - XBlockFixtureDesc('problem', "VISIBLE TO ALPHA", data=problem_data, metadata={"group_access": {0: [0]}}), - ) - self.group_configurations_page.visit() - config = self.group_configurations_page.content_groups[0] - self.assertTrue(config.delete_button_is_disabled) - - def test_can_delete_unused_content_group(self): - """ - Scenario: Ensure that the user can delete unused content group. - Given I have a course with 1 Content Group - And I go to the Group Configuration page - When I delete the Content Group with name "New Content Group" - Then I see that there is no Content Group - When I refresh the page - Then I see that the content group has been deleted - """ - self.group_configurations_page.visit() - config = self.create_and_verify_content_group("New Content Group", 0) - self.assertTrue(config.delete_button_is_present) - - self.assertEqual(len(self.group_configurations_page.content_groups), 1) - - # Delete content group - config.delete() - self.assertEqual(len(self.group_configurations_page.content_groups), 0) - - self.group_configurations_page.visit() - self.assertEqual(len(self.group_configurations_page.content_groups), 0) - - def test_must_supply_name(self): - """ - Scenario: Ensure that validation of the content group works correctly. - Given I have a course without content groups - And I create new content group without specifying a name click the button 'Create' - Then I see error message "Content Group name is required." - When I set a name and click the button 'Create' - Then I see the content group is saved successfully - """ - self.group_configurations_page.visit() - self.group_configurations_page.create_first_content_group() - config = self.group_configurations_page.content_groups[0] - config.save() - self.assertEqual(config.mode, 'edit') - self.assertEqual("Group name is required", config.validation_message) - config.name = "Content Group Name" - config.save() - self.assertIn("Content Group Name", config.name) - - def test_can_cancel_creation_of_content_group(self): - """ - Scenario: Ensure that creation of a content group can be canceled correctly. - Given I have a course without content groups - When I click button 'Add your first Content Group' - And I set new the name and click the button 'Cancel' - Then I see that there is no content groups in the course - """ - self.group_configurations_page.visit() - self.group_configurations_page.create_first_content_group() - config = self.group_configurations_page.content_groups[0] - config.name = "Content Group" - config.cancel() - self.assertEqual(0, len(self.group_configurations_page.content_groups)) - - def test_content_group_empty_usage(self): - """ - Scenario: When content group is not used, ensure that the link to outline page works correctly. - Given I have a course without content group - And I create new content group - Then I see a link to the outline page - When I click on the outline link - Then I see the outline page - """ - self.group_configurations_page.visit() - config = self.create_and_verify_content_group("New Content Group", 0) - config.toggle() - config.click_outline_anchor() - - # Waiting for the page load and verify that we've landed on course outline page - self.outline_page.wait_for_page() @attr('a11y') @@ -326,344 +109,3 @@ class StudioSubsectionSettingsA11yTest(StudioCourseTest): include=['section.edit-settings-timed-examination'] ) self.course_outline.a11y_audit.check_for_accessibility_errors() - - -@attr(shard=15) -class StudioSettingsImageUploadTest(StudioCourseTest): - """ - Class to test course settings image uploads. - """ - def setUp(self): # pylint: disable=arguments-differ - super(StudioSettingsImageUploadTest, self).setUp() - self.settings_page = SettingsPage(self.browser, self.course_info['org'], self.course_info['number'], - self.course_info['run']) - self.settings_page.visit() - - # Ensure jquery is loaded before running a jQuery - self.settings_page.wait_for_ajax() - # This text appears towards the end of the work that jQuery is performing on the page - self.settings_page.wait_for_jquery_value('input#course-name:text', 'test_run') - - def test_upload_course_card_image(self): - - # upload image - file_to_upload = 'image.jpg' - self.settings_page.upload_image('#upload-course-image', file_to_upload) - self.assertIn(file_to_upload, self.settings_page.get_uploaded_image_path('#course-image')) - - def test_upload_course_banner_image(self): - - # upload image - file_to_upload = 'image.jpg' - self.settings_page.upload_image('#upload-banner-image', file_to_upload) - self.assertIn(file_to_upload, self.settings_page.get_uploaded_image_path('#banner-image')) - - def test_upload_course_video_thumbnail_image(self): - - # upload image - file_to_upload = 'image.jpg' - self.settings_page.upload_image('#upload-video-thumbnail-image', file_to_upload) - self.assertIn(file_to_upload, self.settings_page.get_uploaded_image_path('#video-thumbnail-image')) - - -@attr(shard=16) -class CourseSettingsTest(StudioCourseTest): - """ - Class to test course settings. - """ - COURSE_START_DATE_CSS = "#course-start-date" - COURSE_END_DATE_CSS = "#course-end-date" - ENROLLMENT_START_DATE_CSS = "#course-enrollment-start-date" - ENROLLMENT_END_DATE_CSS = "#course-enrollment-end-date" - - COURSE_START_TIME_CSS = "#course-start-time" - COURSE_END_TIME_CSS = "#course-end-time" - ENROLLMENT_START_TIME_CSS = "#course-enrollment-start-time" - ENROLLMENT_END_TIME_CSS = "#course-enrollment-end-time" - - course_start_date = '12/20/2013' - course_end_date = '12/26/2013' - enrollment_start_date = '12/01/2013' - enrollment_end_date = '12/10/2013' - - dummy_time = "15:30" - - def setUp(self, is_staff=False, test_xss=True): - super(CourseSettingsTest, self).setUp() - - self.settings_page = SettingsPage( - self.browser, - self.course_info['org'], - self.course_info['number'], - self.course_info['run'] - ) - - # Before every test, make sure to visit the page first - self.settings_page.visit() - self.ensure_input_fields_are_loaded() - - def set_course_dates(self): - """ - Set dates for the course. - """ - dates_dictionary = { - self.COURSE_START_DATE_CSS: self.course_start_date, - self.COURSE_END_DATE_CSS: self.course_end_date, - self.ENROLLMENT_START_DATE_CSS: self.enrollment_start_date, - self.ENROLLMENT_END_DATE_CSS: self.enrollment_end_date - } - - self.settings_page.set_element_values(dates_dictionary) - - def ensure_input_fields_are_loaded(self): - """ - Ensures values in input fields are loaded. - """ - EmptyPromise( - lambda: self.settings_page.q(css='#course-organization').attrs('value')[0], - "Waiting for input fields to be loaded" - ).fulfill() - - def test_user_can_set_course_date(self): - """ - 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 - And I press the "Save" notification button - And I reload the page - Then I see the set dates - """ - - # Set dates - self.set_course_dates() - # Set times - time_dictionary = { - self.COURSE_START_TIME_CSS: self.dummy_time, - self.ENROLLMENT_END_TIME_CSS: self.dummy_time - } - self.settings_page.set_element_values(time_dictionary) - # Save changes - self.settings_page.save_changes() - self.settings_page.refresh_and_wait_for_load() - self.ensure_input_fields_are_loaded() - css_selectors = [self.COURSE_START_DATE_CSS, self.COURSE_END_DATE_CSS, - self.ENROLLMENT_START_DATE_CSS, self.ENROLLMENT_END_DATE_CSS, - self.COURSE_START_TIME_CSS, self.ENROLLMENT_END_TIME_CSS] - - expected_values = [self.course_start_date, self.course_end_date, - self.enrollment_start_date, self.enrollment_end_date, - self.dummy_time, self.dummy_time] - # Assert changes have been persistent. - self.assertEqual( - [get_input_value(self.settings_page, css_selector) for css_selector in css_selectors], - expected_values - ) - - def test_clear_previously_set_course_dates(self): - """ - 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 - And I press the "Save" notification button - And I reload the page - Then I see cleared dates - """ - - # Set dates - self.set_course_dates() - # Clear all dates except start date - values_to_set = { - self.COURSE_END_DATE_CSS: '', - self.ENROLLMENT_START_DATE_CSS: '', - self.ENROLLMENT_END_DATE_CSS: '' - } - self.settings_page.set_element_values(values_to_set) - # Save changes and refresh the page - self.settings_page.save_changes() - self.settings_page.refresh_and_wait_for_load() - self.ensure_input_fields_are_loaded() - css_selectors = [self.COURSE_START_DATE_CSS, self.COURSE_END_DATE_CSS, - self.ENROLLMENT_START_DATE_CSS, self.ENROLLMENT_END_DATE_CSS] - - expected_values = [self.course_start_date, '', '', ''] - # Assert changes have been persistent. - self.assertEqual( - [get_input_value(self.settings_page, css_selector) for css_selector in css_selectors], - expected_values - ) - - def test_cannot_clear_the_course_start_date(self): - """ - Scenario: User cannot clear the course start date - Given I have set course dates - And I press the "Save" notification button - And I clear the course start date - Then I receive a warning about course start date - And I reload the page - And the previously set start date is shown - """ - # Set dates - self.set_course_dates() - # Save changes - self.settings_page.save_changes() - # Get default start date - default_start_date = get_input_value(self.settings_page, self.COURSE_START_DATE_CSS) - # Set course start date to empty - self.settings_page.set_element_values({self.COURSE_START_DATE_CSS: ''}) - # Make sure error message is show with appropriate message - error_message_css = '.message-error' - self.settings_page.wait_for_element_presence(error_message_css, 'Error message is present') - self.assertEqual(element_has_text(self.settings_page, error_message_css, - "The course must have an assigned start date."), True) - # Refresh the page and assert start date has not changed. - self.settings_page.refresh_and_wait_for_load() - self.ensure_input_fields_are_loaded() - self.assertEqual( - get_input_value(self.settings_page, self.COURSE_START_DATE_CSS), - default_start_date - ) - - def test_user_can_correct_course_start_date_warning(self): - """ - Scenario: User can correct the course start date warning - Given I have tried to clear the course start - And I have entered a new course start date - And I press the "Save" notification button - Then The warning about course start date goes away - And I reload the page - Then my new course start date is shown - """ - # Set course start date to empty - self.settings_page.set_element_values({self.COURSE_START_DATE_CSS: ''}) - # Make sure we get error message - error_message_css = '.message-error' - self.settings_page.wait_for_element_presence(error_message_css, 'Error message is present') - self.assertEqual(element_has_text(self.settings_page, error_message_css, - "The course must have an assigned start date."), True) - # Set new course start value - self.settings_page.set_element_values({self.COURSE_START_DATE_CSS: self.course_start_date}) - self.settings_page.un_focus_input_field() - # Error message disappears - self.settings_page.wait_for_element_absence(error_message_css, 'Error message is not present') - # Save the changes and refresh the page. - self.settings_page.save_changes() - self.settings_page.refresh_and_wait_for_load() - self.ensure_input_fields_are_loaded() - # Assert changes are persistent. - self.assertEqual( - get_input_value(self.settings_page, self.COURSE_START_DATE_CSS), - self.course_start_date - ) - - def test_settings_are_only_persisted_when_saved(self): - """ - Scenario: Settings are only persisted when saved - Given I have set course dates - And I press the "Save" notification button - When I change fields - And I reload the page - Then I do not see the changes - """ - # Set course dates. - self.set_course_dates() - # Save changes. - self.settings_page.save_changes() - default_value_enrollment_start_date = get_input_value(self.settings_page, - self.ENROLLMENT_START_TIME_CSS) - # Set the value of enrollment start time and - # reload the page without saving. - self.settings_page.set_element_values({self.ENROLLMENT_START_TIME_CSS: self.dummy_time}) - self.settings_page.refresh_and_wait_for_load() - self.ensure_input_fields_are_loaded() - - css_selectors = [self.COURSE_START_DATE_CSS, self.COURSE_END_DATE_CSS, - self.ENROLLMENT_START_DATE_CSS, self.ENROLLMENT_END_DATE_CSS, - self.ENROLLMENT_START_TIME_CSS] - - expected_values = [self.course_start_date, self.course_end_date, - self.enrollment_start_date, self.enrollment_end_date, - default_value_enrollment_start_date] - # Assert that value of enrolment start time - # is not saved. - self.assertEqual( - [get_input_value(self.settings_page, css_selector) for css_selector in css_selectors], - expected_values - ) - - def test_settings_are_reset_on_cancel(self): - """ - Scenario: Settings are reset on cancel - Given I have set course dates - And I press the "Save" notification button - When I change fields - And I press the "Cancel" notification button - Then I do not see the changes - """ - # Set course date - self.set_course_dates() - # Save changes - self.settings_page.save_changes() - default_value_enrollment_start_date = get_input_value(self.settings_page, - self.ENROLLMENT_START_TIME_CSS) - # Set value but don't save it. - self.settings_page.set_element_values({self.ENROLLMENT_START_TIME_CSS: self.dummy_time}) - self.settings_page.click_button("cancel") - # Make sure changes are not saved after cancel. - css_selectors = [self.COURSE_START_DATE_CSS, self.COURSE_END_DATE_CSS, - self.ENROLLMENT_START_DATE_CSS, self.ENROLLMENT_END_DATE_CSS, - self.ENROLLMENT_START_TIME_CSS] - - expected_values = [self.course_start_date, self.course_end_date, - self.enrollment_start_date, self.enrollment_end_date, - default_value_enrollment_start_date] - - self.assertEqual( - [get_input_value(self.settings_page, css_selector) for css_selector in css_selectors], - expected_values - ) - - def test_confirmation_is_shown_on_save(self): - """ - Scenario: Confirmation is shown on save - Given I have opened a new course in Studio - When I select Schedule and Details - And I change the "Choose Yes.
-- -- -Yes -" field to " " - And I press the "Save" notification button - Then I see a confirmation that my changes have been saved - """ - # Set date - self.settings_page.set_element_values({self.COURSE_START_DATE_CSS: self.course_start_date}) - # Confirmation is showed upon save. - # Save_changes function ensures that save - # confirmation is shown. - self.settings_page.save_changes() - - def test_changes_in_course_overview_show_a_confirmation(self): - """ - Scenario: Changes in Course Overview show a confirmation - Given I have opened a new course in Studio - When I select Schedule and Details - And I change the course overview - And I press the "Save" notification button - Then I see a confirmation that my changes have been saved - """ - # Change the value of course overview - self.settings_page.change_course_description('Changed overview') - # Save changes - # Save_changes function ensures that save - # confirmation is shown. - self.settings_page.save_changes() - - def test_user_cannot_save_invalid_settings(self): - """ - Scenario: User cannot save invalid settings - Given I have opened a new course in Studio - When I select Schedule and Details - And I change the "Course Start Date" field to "" - Then the save notification button is disabled - """ - # Change the course start date to invalid date. - self.settings_page.set_element_values({self.COURSE_START_DATE_CSS: ''}) - # Confirm that save button is disabled. - self.assertEqual(self.settings_page.is_element_present(".action-primary.action-save.is-disabled"), True) diff --git a/common/test/acceptance/tests/studio/test_studio_settings_certificates.py b/common/test/acceptance/tests/studio/test_studio_settings_certificates.py deleted file mode 100644 index 2e8e7a82a5..0000000000 --- a/common/test/acceptance/tests/studio/test_studio_settings_certificates.py +++ /dev/null @@ -1,312 +0,0 @@ -""" -Acceptance tests for Studio's Setting pages -""" - - -import re - -from common.test.acceptance.pages.lms.create_mode import ModeCreationPage -from common.test.acceptance.pages.studio.settings_advanced import AdvancedSettingsPage -from common.test.acceptance.pages.studio.settings_certificates import CertificatesPage -from common.test.acceptance.tests.helpers import skip_if_browser -from common.test.acceptance.tests.studio.base_studio_test import StudioCourseTest - - -class CertificatesTest(StudioCourseTest): - """ - Tests for settings/certificates Page. - """ - shard = 22 - - def setUp(self): # pylint: disable=arguments-differ - super(CertificatesTest, self).setUp(is_staff=True, test_xss=False) - self.certificates_page = CertificatesPage( - self.browser, - self.course_info['org'], - self.course_info['number'], - self.course_info['run'] - ) - self.advanced_settings_page = AdvancedSettingsPage( - self.browser, - self.course_info['org'], - self.course_info['number'], - self.course_info['run'] - ) - self.course_advanced_settings = dict() - - # Add a verified mode to the course - ModeCreationPage( - self.browser, self.course_id, mode_slug=u'verified', mode_display_name=u'Verified Certificate', - min_price=10, suggested_prices='10,20' - ).visit() - - def make_signatory_data(self, prefix='First'): - """ - Makes signatory dict which can be used in the tests to create certificates - """ - return { - 'name': u'{prefix} Signatory Name'.format(prefix=prefix), - 'title': u'{prefix} Signatory Title'.format(prefix=prefix), - 'organization': u'{prefix} Signatory Organization'.format(prefix=prefix), - } - - def create_and_verify_certificate(self, course_title_override, existing_certs, signatories): - """ - Creates a new certificate and verifies that it was properly created. - """ - self.assertEqual(existing_certs, len(self.certificates_page.certificates)) - if existing_certs == 0: - self.certificates_page.wait_for_first_certificate_button() - self.certificates_page.click_first_certificate_button() - else: - self.certificates_page.wait_for_add_certificate_button() - self.certificates_page.click_add_certificate_button() - - certificate = self.certificates_page.certificates[existing_certs] - - # Set the certificate properties - certificate.course_title = course_title_override - - # add signatories - added_signatories = 0 - for idx, signatory in enumerate(signatories): - certificate.signatories[idx].name = signatory['name'] - certificate.signatories[idx].title = signatory['title'] - certificate.signatories[idx].organization = signatory['organization'] - certificate.signatories[idx].upload_signature_image('Signature-{}.png'.format(idx)) - - added_signatories += 1 - if len(signatories) > added_signatories: - certificate.click_add_signatory_button() - - # Save the certificate - self.assertEqual(certificate.get_text('.action-primary'), "Create") - certificate.click_create_certificate_button() - self.assertIn(course_title_override, certificate.course_title) - return certificate - - def test_no_certificates_by_default(self): - """ - Scenario: Ensure that message telling me to create a new certificate is - shown when no certificate exist. - Given I have a course without certificates - When I go to the Certificates page in Studio - Then I see "You have not created any certificates yet." message and - a link with text "Set up your certificate" - """ - self.certificates_page.visit() - self.assertTrue(self.certificates_page.no_certificates_message_shown) - self.assertIn( - "You have not created any certificates yet.", - self.certificates_page.no_certificates_message_text - ) - self.assertIn( - "Set up your certificate", - self.certificates_page.new_certificate_link_text - ) - - def test_can_create_and_edit_certficate(self): - """ - Scenario: Ensure that the certificates can be created and edited correctly. - Given I have a course without certificates - When I click button 'Add your first Certificate' - And I set new the course title override and signatory and click the button 'Create' - Then I see the new certificate is added and has correct data - When I edit the certificate - And I change the name and click the button 'Save' - Then I see the certificate is saved successfully and has the new name - """ - self.certificates_page.visit() - self.certificates_page.wait_for_first_certificate_button() - certificate = self.create_and_verify_certificate( - "Course Title Override", - 0, - [self.make_signatory_data('first'), self.make_signatory_data('second')] - ) - - # Edit the certificate - certificate.click_edit_certificate_button() - certificate.course_title = "Updated Course Title Override 2" - self.assertEqual(certificate.get_text('.action-primary'), "Save") - certificate.click_save_certificate_button() - - self.assertIn("Updated Course Title Override 2", certificate.course_title) - - def test_can_delete_certificate(self): - """ - Scenario: Ensure that the user can delete certificate. - Given I have a course with 1 certificate - And I go to the Certificates page - When I delete the Certificate with name "New Certificate" - Then I see that there is no certificate - When I refresh the page - Then I see that the certificate has been deleted - """ - self.certificates_page.visit() - certificate = self.create_and_verify_certificate( - "Course Title Override", - 0, - [self.make_signatory_data('first'), self.make_signatory_data('second')] - ) - - certificate.wait_for_certificate_delete_button() - - self.assertEqual(len(self.certificates_page.certificates), 1) - - # Delete the certificate we just created - certificate.click_delete_certificate_button() - self.certificates_page.click_confirmation_prompt_primary_button() - - # Reload the page and confirm there are no certificates - self.certificates_page.visit() - self.assertEqual(len(self.certificates_page.certificates), 0) - - @skip_if_browser('chrome') # TODO Need to fix this for chrome browser - def test_can_create_and_edit_signatories_of_certficate(self): - """ - Scenario: Ensure that the certificates can be created with signatories and edited correctly. - Given I have a course without certificates - When I click button 'Add your first Certificate' - And I set new the course title override and signatory and click the button 'Create' - Then I see the new certificate is added and has one signatory inside it - When I click 'Edit' button of signatory panel - And I set the name and click the button 'Save' icon - Then I see the signatory name updated with newly set name - When I refresh the certificates page - Then I can see course has one certificate with new signatory name - When I click 'Edit' button of signatory panel - And click on 'Close' button - Then I can see no change in signatory detail - """ - self.certificates_page.visit() - certificate = self.create_and_verify_certificate( - "Course Title Override", - 0, - [self.make_signatory_data('first')] - ) - self.assertEqual(len(self.certificates_page.certificates), 1) - # Edit the signatory in certificate - signatory = certificate.signatories[0] - signatory.edit() - - signatory.name = 'Updated signatory name' - signatory.title = 'Update signatory title' - signatory.organization = 'Updated signatory organization' - signatory.save() - - self.assertEqual(len(self.certificates_page.certificates), 1) - - #Refreshing the page, So page have the updated certificate object. - self.certificates_page.refresh() - self.certificates_page.wait_for_page() - signatory = self.certificates_page.certificates[0].signatories[0] - self.assertIn("Updated signatory name", signatory.name) - self.assertIn("Update signatory title", signatory.title) - self.assertIn("Updated signatory organization", signatory.organization) - - signatory.edit() - signatory.close() - - self.assertIn("Updated signatory name", signatory.name) - - def test_can_cancel_creation_of_certificate(self): - """ - Scenario: Ensure that creation of a certificate can be canceled correctly. - Given I have a course without certificates - When I click button 'Add your first Certificate' - And I set name of certificate and click the button 'Cancel' - Then I see that there is no certificates in the course - """ - self.certificates_page.visit() - self.certificates_page.click_first_certificate_button() - certificate = self.certificates_page.certificates[0] - certificate.course_title = "Title Override" - certificate.click_cancel_edit_certificate() - self.assertEqual(len(self.certificates_page.certificates), 0) - - def test_line_breaks_in_signatory_title(self): - """ - Scenario: Ensure that line breaks are properly reflected in certificate - - Given I have a certificate with signatories - When I add signatory title with new line character - Then I see line break in certificate title - """ - self.certificates_page.visit() - certificate = self.create_and_verify_certificate( - "Course Title Override", - 0, - [ - { - 'name': 'Signatory Name', - 'title': 'Signatory title with new line character \n', - 'organization': 'Signatory Organization', - } - ] - ) - - certificate.wait_for_certificate_delete_button() - - # Make sure certificate is created - self.assertEqual(len(self.certificates_page.certificates), 1) - - signatory_title = self.certificates_page.get_first_signatory_title() - self.assertNotEqual([], re.findall(r'
', signatory_title)) - - def test_course_number_in_certificate_details_view(self): - """ - Scenario: Ensure that Course Number is displayed in certificate details view - - Given I have a certificate - When I visit certificate details page on studio - Then I see Course Number next to Course Name - """ - self.certificates_page.visit() - certificate = self.create_and_verify_certificate( - "Course Title Override", - 0, - [self.make_signatory_data('first')] - ) - - certificate.wait_for_certificate_delete_button() - - # Make sure certificate is created - self.assertEqual(len(self.certificates_page.certificates), 1) - course_number = self.certificates_page.get_course_number() - self.assertEqual(self.course_info['number'], course_number) - - def test_course_number_override_in_certificate_details_view(self): - """ - Scenario: Ensure that Course Number Override is displayed in certificate details view - - Given I have a certificate - When I visit certificate details page on studio then course number override should be hidden. - Then I visit the course advance settings page and set the value for course override number. - Then I see Course Number Override next to Course Name in certificate settings page. - """ - - self.course_advanced_settings.update( - {'Course Number Display String': 'Course Number Override String'} - ) - - self.certificates_page.visit() - certificate = self.create_and_verify_certificate( - "Course Title Override", - 0, - [self.make_signatory_data('first')] - ) - self.assertFalse(self.certificates_page.course_number_override().present) - certificate.wait_for_certificate_delete_button() - - # Make sure certificate is created - self.assertEqual(len(self.certificates_page.certificates), 1) - - # set up course number override in Advanced Settings Page - self.advanced_settings_page.visit() - self.advanced_settings_page.set_values(self.course_advanced_settings) - self.advanced_settings_page.wait_for_ajax() - - self.certificates_page.visit() - course_number_override = self.certificates_page.get_course_number_override() - self.assertEqual(self.course_advanced_settings['Course Number Display String'], course_number_override) - self.assertTrue(self.certificates_page.course_number_override().present) diff --git a/common/test/acceptance/tests/studio/test_studio_settings_details.py b/common/test/acceptance/tests/studio/test_studio_settings_details.py deleted file mode 100644 index e67ee481da..0000000000 --- a/common/test/acceptance/tests/studio/test_studio_settings_details.py +++ /dev/null @@ -1,219 +0,0 @@ -""" -Acceptance tests for Studio's Settings Details pages -""" - - -from datetime import datetime, timedelta - -import six - -from common.test.acceptance.fixtures.config import ConfigModelFixture -from common.test.acceptance.fixtures.course import CourseFixture -from common.test.acceptance.pages.studio.overview import CourseOutlinePage -from common.test.acceptance.pages.studio.settings import SettingsPage -from common.test.acceptance.tests.helpers import ( - element_has_text, - generate_course_key, - is_option_value_selected, - select_option_by_value -) -from common.test.acceptance.tests.studio.base_studio_test import StudioCourseTest - - -class StudioSettingsDetailsTest(StudioCourseTest): - """Base class for settings and details page tests.""" - shard = 4 - - def setUp(self, is_staff=True): - super(StudioSettingsDetailsTest, self).setUp(is_staff=is_staff) - self.settings_detail = SettingsPage( - self.browser, - self.course_info['org'], - self.course_info['number'], - self.course_info['run'] - ) - - # Before every test, make sure to visit the page first - self.settings_detail.visit() - - -class SettingsMilestonesTest(StudioSettingsDetailsTest): - """ - Tests for milestones feature in Studio's settings tab - """ - shard = 4 - - def test_page_has_prerequisite_field(self): - """ - Test to make sure page has pre-requisite course field if milestones app is enabled. - """ - - self.assertTrue(self.settings_detail.pre_requisite_course_options) - - def test_prerequisite_course_save_successfully(self): - """ - Scenario: Selecting course from Pre-Requisite course drop down save the selected course as pre-requisite - course. - Given that I am on the Schedule & Details page on studio - When I select an item in pre-requisite course drop down and click Save Changes button - Then My selected item should be saved as pre-requisite course - And My selected item should be selected after refreshing the page.' - """ - course_number = self.unique_id - CourseFixture( - org='test_org', - number=course_number, - run='test_run', - display_name='Test Course' + course_number - ).install() - - pre_requisite_course_key = generate_course_key( - org='test_org', - number=course_number, - run='test_run' - ) - pre_requisite_course_id = six.text_type(pre_requisite_course_key) - - # Refresh the page to load the new course fixture and populate the prrequisite course dropdown - # Then select the prerequisite course and save the changes - self.settings_detail.refresh_page() - self.settings_detail.wait_for_prerequisite_course_options() - select_option_by_value( - browser_query=self.settings_detail.pre_requisite_course_options, - value=pre_requisite_course_id - ) - self.settings_detail.save_changes() - self.assertEqual( - 'Your changes have been saved.', - self.settings_detail.alert_confirmation_title.text - ) - - # Refresh the page again and confirm the prerequisite course selection is properly reflected - self.settings_detail.refresh_page() - self.settings_detail.wait_for_prerequisite_course_options() - self.assertTrue(is_option_value_selected( - browser_query=self.settings_detail.pre_requisite_course_options, - value=pre_requisite_course_id - )) - - # Set the prerequisite course back to None and save the changes - select_option_by_value( - browser_query=self.settings_detail.pre_requisite_course_options, - value='' - ) - self.settings_detail.save_changes() - self.assertEqual( - 'Your changes have been saved.', - self.settings_detail.alert_confirmation_title.text - ) - - # Refresh the page again to confirm the None selection is properly reflected - self.settings_detail.refresh_page() - self.settings_detail.wait_for_prerequisite_course_options() - self.assertTrue(is_option_value_selected( - browser_query=self.settings_detail.pre_requisite_course_options, - value='' - )) - - # Re-pick the prerequisite course and confirm no errors are thrown (covers a discovered bug) - select_option_by_value( - browser_query=self.settings_detail.pre_requisite_course_options, - value=pre_requisite_course_id - ) - self.settings_detail.save_changes() - self.assertEqual( - 'Your changes have been saved.', - self.settings_detail.alert_confirmation_title.text - ) - - # Refresh the page again to confirm the prerequisite course selection is properly reflected - self.settings_detail.refresh_page() - self.settings_detail.wait_for_prerequisite_course_options() - dropdown_status = is_option_value_selected( - browser_query=self.settings_detail.pre_requisite_course_options, - value=pre_requisite_course_id - ) - self.assertTrue(dropdown_status) - - def test_page_has_enable_entrance_exam_field(self): - """ - Test to make sure page has 'enable entrance exam' field. - """ - self.assertTrue(self.settings_detail.entrance_exam_field) - - def test_entrance_exam_has_unit_button(self): - """ - Test that entrance exam should be created after checking the 'enable entrance exam' checkbox. - And user has option to add units only instead of any Subsection. - """ - self.settings_detail.require_entrance_exam(required=True) - self.settings_detail.save_changes() - - # getting the course outline page. - course_outline_page = CourseOutlinePage( - self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run'] - ) - course_outline_page.visit() - course_outline_page.wait_for_ajax() - - # button with text 'New Unit' should be present. - self.assertTrue(element_has_text( - page=course_outline_page, - css_selector='.add-item a.button-new', - text='New Unit' - )) - - # button with text 'New Subsection' should not be present. - self.assertFalse(element_has_text( - page=course_outline_page, - css_selector='.add-item a.button-new', - text='New Subsection' - )) - - -class CoursePacingTest(StudioSettingsDetailsTest): - """Tests for setting a course to self-paced.""" - shard = 4 - - def populate_course_fixture(self, __): - ConfigModelFixture('/config/self_paced', {'enabled': True}).install() - # Set the course start date to tomorrow in order to allow setting pacing - self.course_fixture.add_course_details({'start_date': datetime.now() + timedelta(days=1)}) - - def test_default_instructor_paced(self): - """ - Test that the 'instructor paced' button is checked by default. - """ - self.assertEqual(self.settings_detail.course_pacing, 'Instructor-Paced') - - def test_self_paced(self): - """ - Test that the 'self-paced' button is checked for a self-paced - course. - """ - self.course_fixture.add_course_details({ - 'self_paced': True - }) - self.course_fixture.configure_course() - self.settings_detail.refresh_page() - self.assertEqual(self.settings_detail.course_pacing, 'Self-Paced') - - def test_set_self_paced(self): - """ - Test that the self-paced option is persisted correctly. - """ - self.settings_detail.course_pacing = 'Self-Paced' - self.settings_detail.save_changes() - self.settings_detail.refresh_page() - self.assertEqual(self.settings_detail.course_pacing, 'Self-Paced') - - def test_toggle_pacing_after_course_start(self): - """ - Test that course authors cannot toggle the pacing of their course - while the course is running. - """ - self.course_fixture.add_course_details({'start_date': datetime.now()}) - self.course_fixture.configure_course() - self.settings_detail.refresh_page() - self.assertTrue(self.settings_detail.course_pacing_disabled()) - self.assertIn('Course pacing cannot be changed', self.settings_detail.course_pacing_disabled_text) diff --git a/common/test/acceptance/tests/test_cohorted_courseware.py b/common/test/acceptance/tests/test_cohorted_courseware.py deleted file mode 100644 index d8104527cb..0000000000 --- a/common/test/acceptance/tests/test_cohorted_courseware.py +++ /dev/null @@ -1,261 +0,0 @@ -""" -End-to-end test for cohorted courseware. This uses both Studio and LMS. -""" - - -from bok_choy.page_object import XSS_INJECTION - -from common.test.acceptance.fixtures.course import XBlockFixtureDesc -from common.test.acceptance.pages.common.auto_auth import AutoAuthPage -from common.test.acceptance.pages.common.utils import add_enrollment_course_modes, enroll_user_track -from common.test.acceptance.pages.lms.courseware import CoursewarePage -from common.test.acceptance.pages.lms.instructor_dashboard import InstructorDashboardPage -from common.test.acceptance.pages.studio.settings_group_configurations import GroupConfigurationsPage -from common.test.acceptance.pages.studio.xblock_editor import XBlockVisibilityEditorView -from common.test.acceptance.tests.discussion.helpers import CohortTestMixin -from common.test.acceptance.tests.lms.test_lms_user_preview import verify_expected_problem_visibility - -from .studio.base_studio_test import ContainerBase - -AUDIT_TRACK = "Audit" -VERIFIED_TRACK = "Verified" - - -class EndToEndCohortedCoursewareTest(ContainerBase, CohortTestMixin): - """ - End-to-end of cohorted courseware. - """ - shard = 5 - - def setUp(self, is_staff=True): - - super(EndToEndCohortedCoursewareTest, self).setUp(is_staff=is_staff) - self.staff_user = self.user - - self.content_group_a = "Content Group A" + XSS_INJECTION - self.content_group_b = "Content Group B" + XSS_INJECTION - - # Creates the Course modes needed to test enrollment tracks - add_enrollment_course_modes(self.browser, self.course_id, ["audit", "verified"]) - - # Create a student who will be in "Cohort A" - self.cohort_a_student_username = "cohort_a_student" - self.cohort_a_student_email = "cohort_a_student@example.com" - AutoAuthPage( - self.browser, username=self.cohort_a_student_username, email=self.cohort_a_student_email, no_login=True - ).visit() - - # Create a student who will be in "Cohort B" - self.cohort_b_student_username = "cohort_b_student" - self.cohort_b_student_email = "cohort_b_student@example.com" - AutoAuthPage( - self.browser, username=self.cohort_b_student_username, email=self.cohort_b_student_email, no_login=True - ).visit() - - # Create a Verified Student - self.cohort_verified_student_username = "cohort_verified_student" - self.cohort_verified_student_email = "cohort_verified_student@example.com" - AutoAuthPage( - self.browser, - username=self.cohort_verified_student_username, - email=self.cohort_verified_student_email, - no_login=True - ).visit() - - # Create audit student - self.cohort_audit_student_username = "cohort_audit_student" - self.cohort_audit_student_email = "cohort_audit_student@example.com" - AutoAuthPage( - self.browser, - username=self.cohort_audit_student_username, - email=self.cohort_audit_student_email, - no_login=True - ).visit() - - # Create a student who will end up in the default cohort group - self.cohort_default_student_username = "cohort_default_student" - self.cohort_default_student_email = "cohort_default_student@example.com" - AutoAuthPage( - self.browser, username=self.cohort_default_student_username, - email=self.cohort_default_student_email, no_login=True - ).visit() - - # Start logged in as the staff user. - AutoAuthPage( - self.browser, username=self.staff_user["username"], email=self.staff_user["email"] - ).visit() - - def populate_course_fixture(self, course_fixture): - """ - Populate the children of the test course fixture. - """ - self.group_a_problem = 'GROUP A CONTENT' - self.group_b_problem = 'GROUP B CONTENT' - self.group_verified_problem = 'GROUP VERIFIED CONTENT' - self.group_audit_problem = 'GROUP AUDIT CONTENT' - - self.group_a_and_b_problem = 'GROUP A AND B CONTENT' - - self.visible_to_all_problem = 'VISIBLE TO ALL CONTENT' - course_fixture.add_children( - XBlockFixtureDesc('chapter', 'Test Section').add_children( - XBlockFixtureDesc('sequential', 'Test Subsection').add_children( - XBlockFixtureDesc('vertical', 'Test Unit').add_children( - XBlockFixtureDesc('problem', self.group_a_problem, data=''), - XBlockFixtureDesc('problem', self.group_b_problem, data=' '), - XBlockFixtureDesc('problem', self.group_verified_problem, data=' '), - XBlockFixtureDesc('problem', self.group_audit_problem, data=' '), - XBlockFixtureDesc('problem', self.group_a_and_b_problem, data=' '), - XBlockFixtureDesc('problem', self.visible_to_all_problem, data=' ') - ) - ) - ) - ) - - def create_content_groups(self): - """ - Creates two content groups in Studio Group Configurations Settings. - """ - group_configurations_page = GroupConfigurationsPage( - self.browser, - self.course_info['org'], - self.course_info['number'], - self.course_info['run'] - ) - group_configurations_page.visit() - - group_configurations_page.create_first_content_group() - config = group_configurations_page.content_groups[0] - config.name = self.content_group_a - config.save() - - group_configurations_page.add_content_group() - config = group_configurations_page.content_groups[1] - config.name = self.content_group_b - config.save() - - def link_problems_to_content_groups_and_publish(self): - """ - Updates 5 of the 6 existing problems to limit their visibility by content group. - Publishes the modified units. - """ - container_page = self.go_to_unit_page() - enrollment_group = 'enrollment_track_group' - - def set_visibility(problem_index, groups, group_partition='content_group'): - problem = container_page.xblocks[problem_index] - problem.edit_visibility() - visibility_dialog = XBlockVisibilityEditorView(self.browser, problem.locator) - partition_name = (visibility_dialog.ENROLLMENT_TRACK_PARTITION - if group_partition == enrollment_group - else visibility_dialog.CONTENT_GROUP_PARTITION) - visibility_dialog.select_groups_in_partition_scheme(partition_name, groups) - - set_visibility(1, [self.content_group_a]) - set_visibility(2, [self.content_group_b]) - set_visibility(3, [VERIFIED_TRACK], enrollment_group) - set_visibility(4, [AUDIT_TRACK], enrollment_group) - set_visibility(5, [self.content_group_a, self.content_group_b]) - - container_page.publish() - - def create_cohorts_and_assign_students(self): - """ - Adds 2 manual cohorts, linked to content groups, to the course. - Each cohort is assigned one student. - """ - instructor_dashboard_page = InstructorDashboardPage(self.browser, self.course_id) - instructor_dashboard_page.visit() - cohort_management_page = instructor_dashboard_page.select_cohort_management() - - def add_cohort_with_student(cohort_name, content_group, student): - cohort_management_page.add_cohort(cohort_name, content_group=content_group) - cohort_management_page.add_students_to_selected_cohort([student]) - - add_cohort_with_student("Cohort A", self.content_group_a, self.cohort_a_student_username) - add_cohort_with_student("Cohort B", self.content_group_b, self.cohort_b_student_username) - - def view_cohorted_content_as_different_users(self): - """ - View content as staff, student in Cohort A, student in Cohort B, Verified Student, Audit student, - and student in Default Cohort. - """ - courseware_page = CoursewarePage(self.browser, self.course_id) - - def login_and_verify_visible_problems(username, email, expected_problems, track=None): - AutoAuthPage( - self.browser, username=username, email=email, course_id=self.course_id - ).visit() - if track is not None: - enroll_user_track(self.browser, self.course_id, track) - courseware_page.visit() - verify_expected_problem_visibility(self, courseware_page, expected_problems) - - login_and_verify_visible_problems( - self.staff_user["username"], self.staff_user["email"], - [self.group_a_problem, - self.group_b_problem, - self.group_verified_problem, - self.group_audit_problem, - self.group_a_and_b_problem, - self.visible_to_all_problem - ], - ) - - login_and_verify_visible_problems( - self.cohort_a_student_username, self.cohort_a_student_email, - [self.group_a_problem, self.group_audit_problem, self.group_a_and_b_problem, self.visible_to_all_problem] - ) - - login_and_verify_visible_problems( - self.cohort_b_student_username, self.cohort_b_student_email, - [self.group_b_problem, self.group_audit_problem, self.group_a_and_b_problem, self.visible_to_all_problem] - ) - - login_and_verify_visible_problems( - self.cohort_verified_student_username, self.cohort_verified_student_email, - [self.group_verified_problem, self.visible_to_all_problem], - 'verified' - ) - - login_and_verify_visible_problems( - self.cohort_audit_student_username, self.cohort_audit_student_email, - [self.group_audit_problem, self.visible_to_all_problem], - 'audit' - ) - - login_and_verify_visible_problems( - self.cohort_default_student_username, self.cohort_default_student_email, - [self.group_audit_problem, self.visible_to_all_problem], - ) - - def test_cohorted_courseware(self): - """ - Scenario: Can create content that is only visible to students in particular cohorts - Given that I have course with 6 problems, 1 staff member, and 6 students - When I enable cohorts in the course - And I add the Course Modes for Verified and Audit - And I create two content groups, Content Group A, and Content Group B, in the course - And I link one problem to Content Group A - And I link one problem to Content Group B - And I link one problem to the Verified Group - And I link one problem to the Audit Group - And I link one problem to both Content Group A and Content Group B - And one problem remains unlinked to any Content Group - And I create two manual cohorts, Cohort A and Cohort B, - linked to Content Group A and Content Group B, respectively - And I assign one student to each manual cohort - And I assign one student to each enrollment track - And one student remains in the default cohort - Then the staff member can see all 6 problems - And the student in Cohort A can see all the problems linked to A - And the student in Cohort B can see all the problems linked to B - And the student in Verified can see the problems linked to Verified and those not linked to a Group - And the student in Audit can see the problems linked to Audit and those not linked to a Group - And the student in the default cohort can ony see the problem that is unlinked to any Content Group - """ - self.enable_cohorting(self.course_fixture) - self.create_content_groups() - self.link_problems_to_content_groups_and_publish() - self.create_cohorts_and_assign_students() - self.view_cohorted_content_as_different_users() diff --git a/common/test/acceptance/tests/video/test_studio_video_editor.py b/common/test/acceptance/tests/video/test_studio_video_editor.py deleted file mode 100644 index 951021a871..0000000000 --- a/common/test/acceptance/tests/video/test_studio_video_editor.py +++ /dev/null @@ -1,466 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -Acceptance tests for CMS Video Editor. -""" - - -import ddt - -from common.test.acceptance.pages.common.utils import confirm_prompt -from common.test.acceptance.tests.video.test_studio_video_module import CMSVideoBaseTest - - -@ddt.ddt -class VideoEditorTest(CMSVideoBaseTest): - """ - CMS Video Editor Test Class - """ - shard = 6 - - def _create_video_component(self, subtitles=False): - """ - Create a video component and navigate to unit page - - Arguments: - subtitles (bool): Upload subtitles or not - - """ - if subtitles: - self.assets.append('subs_3_yD_cEKoCk.srt.sjson') - - self.navigate_to_course_unit() - - def test_default_settings(self): - """ - Scenario: User can view Video metadata - Given I have created a Video component - And I edit the component - Then I see the correct video settings and default values - """ - self._create_video_component() - self.edit_component() - self.assertTrue(self.video.verify_settings()) - - def test_modify_video_display_name(self): - """ - Scenario: User can modify Video display name - Given I have created a Video component - And I edit the component - And I open tab "Advanced" - Then I can modify video display name - And my video display name change is persisted on save - """ - self._create_video_component() - self.edit_component() - self.open_advanced_tab() - self.video.set_field_value('Component Display Name', 'Transformers') - self.save_unit_settings() - self.edit_component() - self.open_advanced_tab() - self.assertTrue(self.video.verify_field_value('Component Display Name', 'Transformers')) - - def test_hidden_captions(self): - """ - Scenario: Captions are hidden when "transcript display" is false - Given I have created a Video component with subtitles - And I have set "transcript display" to False - Then when I view the video it does not show the captions - """ - self._create_video_component(subtitles=True) - # Prevent cookies from overriding course settings - self.browser.delete_cookie('hide_captions') - self.edit_component() - self.open_advanced_tab() - self.video.set_field_value('Show Transcript', 'False', 'select') - self.save_unit_settings() - self.assertFalse(self.video.is_captions_visible()) - - def test_translations_uploading(self): - """ - Scenario: Translations uploading works correctly - Given I have created a Video component - And I edit the component - And I open tab "Advanced" - And I upload transcript file "chinese_transcripts.srt" for "zh" language code - And I save changes - Then when I view the video it does show the captions - And I see "好 各位同学" text in the captions - And I edit the component - And I open tab "Advanced" - And I see translations for "zh" - And I upload transcript file "uk_transcripts.srt" for "uk" language code - And I save changes - Then when I view the video it does show the captions - And I see "好 各位同学" text in the captions - And video language menu has "uk, zh" translations - """ - self._create_video_component() - self.edit_component() - self.open_advanced_tab() - self.video.upload_translation('chinese_transcripts.srt', 'zh') - self.save_unit_settings() - self.assertTrue(self.video.is_captions_visible()) - unicode_text = u"好 各位同学" - self.assertIn(unicode_text, self.video.captions_text) - self.edit_component() - self.open_advanced_tab() - self.assertEqual(self.video.translations(), ['zh']) - self.video.upload_translation('uk_transcripts.srt', 'uk') - self.save_unit_settings() - self.assertTrue(self.video.is_captions_visible()) - self.assertIn(unicode_text, self.video.captions_text) - self.assertEqual(set(self.video.caption_languages.keys()), {'zh', 'uk'}) - - def test_save_language_upload_no_transcript(self): - """ - Scenario: Transcript language is not shown in language menu if no transcript file is uploaded - Given I have created a Video component - And I edit the component - And I open tab "Advanced" - And I add a language "uk" but do not upload an .srt file - And I save changes - When I view the video language menu - Then I am not able to see the language "uk" translation language - """ - self._create_video_component() - self.edit_component() - self.open_advanced_tab() - language_code = 'uk' - self.video.click_button('translation_add') - translations_count = self.video.translations_count() - self.video.select_translation_language(language_code, translations_count - 1) - self.save_unit_settings() - self.assertNotIn(language_code, list(self.video.caption_languages.keys())) - - def test_upload_large_transcript(self): - """ - Scenario: User can upload transcript file with > 1mb size - Given I have created a Video component - And I edit the component - And I open tab "Advanced" - And I upload transcript file "1mb_transcripts.srt" for "uk" language code - And I save changes - Then when I view the video it does show the captions - And I see "Привіт, edX вітає вас." text in the captions - """ - self._create_video_component() - self.edit_component() - self.open_advanced_tab() - self.video.upload_translation('1mb_transcripts.srt', 'uk') - self.save_unit_settings() - self.video.wait_for(self.video.is_captions_visible, 'Captions are visible', timeout=10) - unicode_text = u"Привіт, edX вітає вас." - self.assertIn(unicode_text, self.video.captions_lines()) - - def test_translations_download_works_w_saving(self): - """ - Scenario: Translations downloading works correctly w/ preliminary saving - Given I have created a Video component - And I edit the component - And I open tab "Advanced" - And I upload transcript files: - |lang_code|filename | - |uk |uk_transcripts.srt | - |zh |chinese_transcripts.srt| - And I save changes - And I edit the component - And I open tab "Advanced" - And I see translations for "uk, zh" - And video language menu has "uk, zh" translations - Then I can download transcript for "zh" language code, that contains text "好 各位同学" - And I can download transcript for "uk" language code, that contains text "Привіт, edX вітає вас." - """ - self._create_video_component() - self.edit_component() - self.open_advanced_tab() - self.video.upload_translation('uk_transcripts.srt', 'uk') - self.video.upload_translation('chinese_transcripts.srt', 'zh') - self.save_unit_settings() - self.edit_component() - self.open_advanced_tab() - self.assertEqual(sorted(self.video.translations()), sorted(['zh', 'uk'])) - self.assertEqual(sorted(list(self.video.caption_languages.keys())), sorted(['zh', 'uk'])) - zh_unicode_text = u"好 各位同学" - self.assertTrue(self.video.download_translation('zh', zh_unicode_text)) - uk_unicode_text = u"Привіт, edX вітає вас." - self.assertTrue(self.video.download_translation('uk', uk_unicode_text)) - - def test_translations_download_works_wo_saving(self): - """ - Scenario: Translations downloading works correctly w/o preliminary saving - Given I have created a Video component - And I edit the component - And I open tab "Advanced" - And I upload transcript files: - |lang_code|filename | - |uk |uk_transcripts.srt | - |zh |chinese_transcripts.srt| - Then I can download transcript for "zh" language code, that contains text "好 各位同学" - And I can download transcript for "uk" language code, that contains text "Привіт, edX вітає вас." - """ - self._create_video_component() - self.edit_component() - self.open_advanced_tab() - self.video.upload_translation('uk_transcripts.srt', 'uk') - self.video.upload_translation('chinese_transcripts.srt', 'zh') - zh_unicode_text = u"好 各位同学" - self.assertTrue(self.video.download_translation('zh', zh_unicode_text)) - uk_unicode_text = u"Привіт, edX вітає вас." - self.assertTrue(self.video.download_translation('uk', uk_unicode_text)) - - def test_translations_remove_works_wo_saving(self): - """ - Scenario: Translations removing works correctly w/o preliminary saving - Given I have created a Video component - And I edit the component - And I open tab "Advanced" - And I upload transcript file "uk_transcripts.srt" for "uk" language code - And I see translations for "uk" - Then I remove translation for "uk" language code - And I save changes - Then when I view the video it does not show the captions - """ - self._create_video_component() - self.edit_component() - self.open_advanced_tab() - self.video.upload_translation('uk_transcripts.srt', 'uk') - self.assertEqual(self.video.translations(), ['uk']) - self.video.remove_translation('uk') - confirm_prompt(self.video) - self.save_unit_settings() - self.assertFalse(self.video.is_captions_visible()) - - def test_translations_entry_remove_works(self): - """ - Scenario: Translations entry removal works correctly when transcript is not uploaded - Given I have created a Video component - And I edit the component - And I open tab "Advanced" - And I click on "+ Add" button for "Transcript Languages" field - Then I click on "Remove" button - And I see newly created entry is removed - """ - self._create_video_component() - self.edit_component() - self.open_advanced_tab() - self.video.click_button("translation_add") - self.assertEqual(self.video.translations_count(), 1) - self.video.remove_translation("") - self.assertEqual(self.video.translations_count(), 0) - - def test_cannot_upload_sjson_translation(self): - """ - Scenario: User cannot upload translations in sjson format - Given I have created a Video component - And I edit the component - And I open tab "Advanced" - And I click button "Add" - And I choose "uk" language code - And I try to upload transcript file "subs_3_yD_cEKoCk.srt.sjson" - Then I see validation error "Only SRT files can be uploaded. Please select a file ending in .srt to upload." - """ - self._create_video_component() - self.edit_component() - self.open_advanced_tab() - self.video.click_button('translation_add') - self.video.select_translation_language('uk') - self.video.upload_asset('subs_3_yD_cEKoCk.srt.sjson', asset_type='transcript') - error_msg = 'Only SRT files can be uploaded. Please select a file ending in .srt to upload.' - self.assertEqual(self.video.upload_status_message, error_msg) - - def test_replace_translation_w_save(self): - """ - Scenario: User can easy replace the translation by another one w/ preliminary saving - Given I have created a Video component - And I edit the component - And I open tab "Advanced" - And I upload transcript file "chinese_transcripts.srt" for "zh" language code - And I save changes - Then when I view the video it does show the captions - And I see "好 各位同学" text in the captions - And I edit the component - And I open tab "Advanced" - And I see translations for "zh" - And I replace transcript file for "zh" language code by "uk_transcripts.srt" - And I save changes - Then when I view the video it does show the captions - And I see "Привіт, edX вітає вас." text in the captions - """ - self._create_video_component() - self.edit_component() - self.open_advanced_tab() - self.video.upload_translation('chinese_transcripts.srt', 'zh') - self.save_unit_settings() - self.assertTrue(self.video.is_captions_visible()) - unicode_text = u"好 各位同学" - self.assertIn(unicode_text, self.video.captions_text) - self.edit_component() - self.open_advanced_tab() - self.assertEqual(self.video.translations(), ['zh']) - self.video.replace_translation('zh', 'uk', 'uk_transcripts.srt') - self.save_unit_settings() - self.assertTrue(self.video.is_captions_visible()) - unicode_text = u"Привіт, edX вітає вас." - self.assertIn(unicode_text, self.video.captions_text) - - def test_replace_translation_wo_save(self): - """ - Scenario: User can easy replace the translation by another one w/o preliminary saving - Given I have created a Video component - And I edit the component - And I open tab "Advanced" - And I upload transcript file "chinese_transcripts.srt" for "zh" language code - And I see translations for "zh" - And I replace transcript file for "zh" language code by "uk_transcripts.srt" - And I save changes - Then when I view the video it does show the captions - And I see "Привіт, edX вітає вас." text in the captions - """ - self._create_video_component() - self.edit_component() - self.open_advanced_tab() - self.video.upload_translation('chinese_transcripts.srt', 'zh') - self.assertEqual(self.video.translations(), ['zh']) - self.video.replace_translation('zh', 'uk', 'uk_transcripts.srt') - self.save_unit_settings() - self.assertTrue(self.video.is_captions_visible()) - unicode_text = u"Привіт, edX вітає вас." - self.assertIn(unicode_text, self.video.captions_text) - - def test_translation_upload_remove_upload(self): - """ - Scenario: Upload "zh" file "A" -> Remove "zh" -> Upload "zh" file "B" - Given I have created a Video component - And I edit the component - And I open tab "Advanced" - And I upload transcript file "chinese_transcripts.srt" for "zh" language code - And I see translations for "zh" - Then I remove translation for "zh" language code - And I upload transcript file "uk_transcripts.srt" for "zh" language code - And I save changes - Then when I view the video it does show the captions - And I see "Привіт, edX вітає вас." text in the captions - """ - self._create_video_component() - self.edit_component() - self.open_advanced_tab() - self.video.upload_translation('chinese_transcripts.srt', 'zh') - self.assertEqual(self.video.translations(), ['zh']) - self.video.remove_translation('zh') - confirm_prompt(self.video) - self.video.upload_translation('uk_transcripts.srt', 'zh') - self.save_unit_settings() - self.assertTrue(self.video.is_captions_visible()) - unicode_text = u"Привіт, edX вітає вас." - self.assertIn(unicode_text, self.video.captions_text) - - def test_select_language_twice(self): - """ - Scenario: User cannot select the same language twice - Given I have created a Video component - And I edit the component - And I open tab "Advanced" - And I click button "Add" - And I choose "zh" language code - And I click button "Add" - Then I cannot choose "zh" language code - """ - self._create_video_component() - self.edit_component() - self.open_advanced_tab() - self.video.click_button('translation_add') - self.video.select_translation_language('zh') - self.video.click_button('translation_add') - self.assertTrue(self.video.is_language_disabled('zh')) - - def test_table_of_contents(self): - """ - Scenario: User can see Abkhazian (ab) language option at the first position - Given I have created a Video component - And I edit the component - And I open tab "Advanced" - And I upload transcript files: - |lang_code|filename | - |uk |uk_transcripts.srt | - |table |chinese_transcripts.srt| - And I save changes - Then when I view the video it does show the captions - And I see "好 各位同学" text in the captions - And video language menu has "table, uk" translations - And I see video language with code "table" at position "0" - """ - self._create_video_component() - self.edit_component() - self.open_advanced_tab() - self.video.upload_translation('uk_transcripts.srt', 'uk') - self.video.upload_translation('chinese_transcripts.srt', 'ab') - self.save_unit_settings() - self.assertTrue(self.video.is_captions_visible()) - unicode_text = u"好 各位同学" - self.assertIn(unicode_text, self.video.captions_text) - self.assertEqual(sorted(list(self.video.caption_languages.keys())), sorted([u'ab', u'uk'])) - self.assertEqual(sorted(list(self.video.caption_languages.keys()))[0], 'ab') - - def test_upload_transcript_with_BOM(self): - """ - Scenario: User can upload transcript file with BOM(Byte Order Mark) in it. - Given I have created a Video component - And I edit the component - And I open tab "Advanced" - And I upload transcript file "chinese_transcripts_with_BOM.srt" for "zh" language code - And I save changes - Then when I view the video it does show the captions - And I see "莎拉·佩林 (Sarah Palin)" text in the captions - """ - self._create_video_component() - self.edit_component() - self.open_advanced_tab() - self.video.upload_translation('chinese_transcripts_with_BOM.srt', 'zh') - self.save_unit_settings() - self.assertTrue(self.video.is_captions_visible()) - unicode_text = u"莎拉·佩林 (Sarah Palin)" - self.assertIn(unicode_text, self.video.captions_lines()) - - def test_simplified_and_traditional_chinese_transcripts_uploading(self): - """ - Scenario: Translations uploading works correctly - - Given I have created a Video component - And I edit the component - And I open tab "Advanced" - And I upload transcript file "simplified_chinese.srt" for "zh_HANS" language code - And I save changes - Then when I view the video it does show the captions - And I see "在线学习是革" text in the captions - - And I edit the component - And I open tab "Advanced" - And I upload transcript file "traditional_chinese.srt" for "zh_HANT" language code - And I save changes - Then when I view the video it does show the captions - And I see "在線學習是革" text in the captions - - And video subtitle menu has 'zh_HANS', 'zh_HANT' translations for 'Simplified Chinese' - and 'Traditional Chinese' respectively - """ - self._create_video_component() - - langs_info = [ - ('zh_HANS', 'simplified_chinese.srt', u'在线学习是革'), - ('zh_HANT', 'traditional_chinese.srt', u'在線學習是革') - ] - - for lang_code, lang_file, lang_text in langs_info: - self.edit_component() - self.open_advanced_tab() - self.video.upload_translation(lang_file, lang_code) - self.save_unit_settings() - self.assertTrue(self.video.is_captions_visible()) - # If there is only one language then there will be no subtitle/captions menu - if lang_code == u'zh_HANT': - self.video.select_language(lang_code) - unicode_text = lang_text - self.assertIn(unicode_text, self.video.captions_text) - - self.assertEqual(self.video.caption_languages, {'zh_HANS': 'Simplified Chinese', 'zh_HANT': 'Traditional Chinese'}) diff --git a/common/test/acceptance/tests/video/test_studio_video_module.py b/common/test/acceptance/tests/video/test_studio_video_module.py deleted file mode 100644 index 0cbd37e374..0000000000 --- a/common/test/acceptance/tests/video/test_studio_video_module.py +++ /dev/null @@ -1,371 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -Acceptance tests for CMS Video Module. -""" - - -import os -from unittest import skipIf - -from bok_choy.promise import EmptyPromise -from mock import patch - -from common.test.acceptance.fixtures.course import CourseFixture, XBlockFixtureDesc -from common.test.acceptance.pages.common.auto_auth import AutoAuthPage -from common.test.acceptance.pages.studio.overview import CourseOutlinePage -from common.test.acceptance.pages.studio.video.video import VideoComponentPage -from common.test.acceptance.tests.helpers import UniqueCourseTest, YouTubeStubConfig, is_youtube_available - - -@skipIf(is_youtube_available() is False, 'YouTube is not available!') -class CMSVideoBaseTest(UniqueCourseTest): - """ - CMS Video Module Base Test Class - """ - - def setUp(self): - """ - Initialization of pages and course fixture for tests - """ - super(CMSVideoBaseTest, self).setUp() - - self.video = VideoComponentPage(self.browser) - - # This will be initialized later - self.unit_page = None - - self.outline = CourseOutlinePage( - self.browser, - self.course_info['org'], - self.course_info['number'], - self.course_info['run'] - ) - - self.course_fixture = CourseFixture( - self.course_info['org'], self.course_info['number'], - self.course_info['run'], self.course_info['display_name'] - ) - - self.assets = [] - self.metadata = None - self.addCleanup(YouTubeStubConfig.reset) - - def _create_course_unit(self, youtube_stub_config=None, subtitles=False): - """ - Create a Studio Video Course Unit and Navigate to it. - - Arguments: - youtube_stub_config (dict) - subtitles (bool) - - """ - if youtube_stub_config: - YouTubeStubConfig.configure(youtube_stub_config) - - if subtitles: - self.assets.append('subs_3_yD_cEKoCk.srt.sjson') - - self.navigate_to_course_unit() - - def _create_video(self): - """ - Create Xblock Video Component. - """ - self.video.create_video() - - video_xblocks = self.video.xblocks() - - # Total video xblock components count should be equals to 2 - # Why 2? One video component is created by default for each test. Please see - # test_studio_video_module.py:CMSVideoTest._create_course_unit - # And we are creating second video component here. - self.assertEqual(video_xblocks, 2) - - def _install_course_fixture(self): - """ - Prepare for tests by creating a course with a section, subsection, and unit. - Performs the following: - Create a course with a section, subsection, and unit - Create a user and make that user a course author - Log the user into studio - """ - - if self.assets: - self.course_fixture.add_asset(self.assets) - - # Create course with Video component - self.course_fixture.add_children( - XBlockFixtureDesc('chapter', 'Test Section').add_children( - XBlockFixtureDesc('sequential', 'Test Subsection').add_children( - XBlockFixtureDesc('vertical', 'Test Unit').add_children( - XBlockFixtureDesc('video', 'Video', metadata=self.metadata) - ) - ) - ) - ).install() - - # Auto login and register the course - AutoAuthPage( - self.browser, - staff=False, - username=self.course_fixture.user.get('username'), - email=self.course_fixture.user.get('email'), - password=self.course_fixture.user.get('password') - ).visit() - - def _navigate_to_course_unit_page(self): - """ - Open the course from the dashboard and expand the section and subsection and click on the Unit link - The end result is the page where the user is editing the newly created unit - """ - # Visit Course Outline page - self.outline.visit() - - # Visit Unit page - self.unit_page = self.outline.section('Test Section').subsection('Test Subsection').expand_subsection().unit( - 'Test Unit').go_to() - - self.video.wait_for_video_component_render() - - def navigate_to_course_unit(self): - """ - Install the course with required components and navigate to course unit page - """ - self._install_course_fixture() - self._navigate_to_course_unit_page() - - def edit_component(self, xblock_index=1): - """ - Open component Edit Dialog for first component on page. - - Arguments: - xblock_index: number starting from 1 (0th entry is the unit page itself) - """ - self.unit_page.xblocks[xblock_index].edit() - EmptyPromise( - lambda: self.video.q(css='div.basic_metadata_edit').visible, - "Wait for the basic editor to be open", - timeout=5 - ).fulfill() - - def open_advanced_tab(self): - """ - Open components advanced tab. - """ - # The 0th entry is the unit page itself. - self.unit_page.xblocks[1].open_advanced_tab() - - def open_basic_tab(self): - """ - Open components basic tab. - """ - # The 0th entry is the unit page itself. - self.unit_page.xblocks[1].open_basic_tab() - - def save_unit_settings(self): - """ - Save component settings. - """ - # The 0th entry is the unit page itself. - self.unit_page.xblocks[1].save_settings() - - -class CMSVideoTest(CMSVideoBaseTest): - """ - CMS Video Test Class - """ - shard = 13 - - def test_youtube_stub_proxy(self): - """ - Scenario: YouTube stub server proxies YouTube API correctly - Given youtube stub server proxies YouTube API - And I have created a Video component - Then I can see video button "play" - And I click video button "play" - Then I can see video button "pause" - """ - self._create_course_unit(youtube_stub_config={'youtube_api_blocked': False}) - - self.assertTrue(self.video.is_button_shown('play')) - self.video.click_player_button('play') - self.video.wait_for_state('playing') - self.assertTrue(self.video.is_button_shown('pause')) - - def test_youtube_stub_blocks_youtube_api(self): - """ - Scenario: YouTube stub server can block YouTube API - Given youtube stub server blocks YouTube API - And I have created a Video component - Then I do not see video button "play" - """ - self._create_course_unit(youtube_stub_config={'youtube_api_blocked': True}) - - self.assertFalse(self.video.is_button_shown('play')) - - def test_autoplay_is_disabled(self): - """ - Scenario: Autoplay is disabled in Studio - Given I have created a Video component - Then when I view the video it does not have autoplay enabled - """ - self._create_course_unit() - - self.assertFalse(self.video.is_autoplay_enabled) - - def test_video_creation_takes_single_click(self): - """ - Scenario: Creating a video takes a single click - And creating a video takes a single click - """ - self._create_course_unit() - - # This will create a video by doing a single click and then ensure that video is created - self._create_video() - - def test_captions_hidden_correctly(self): - """ - Scenario: Captions are hidden correctly - Given I have created a Video component with subtitles - And I have hidden captions - Then when I view the video it does not show the captions - """ - self._create_course_unit(subtitles=True) - - self.video.hide_captions() - - self.assertFalse(self.video.is_captions_visible()) - - def test_video_controls_shown_correctly(self): - """ - Scenario: Video controls for all videos show correctly - Given I have created two Video components - And first is private video - When I reload the page - Then video controls for all videos are visible - And the error message isn't shown - """ - self._create_course_unit(youtube_stub_config={'youtube_api_private_video': True}) - self.video.create_video() - - # change id of first default video - self.edit_component(1) - self.open_advanced_tab() - self.video.set_field_value('YouTube ID', 'sampleid123') - self.save_unit_settings() - - # again open unit page and check that video controls show for both videos - self._navigate_to_course_unit_page() - self.assertTrue(self.video.is_controls_visible()) - - # verify that the error message isn't shown by default - self.assertFalse(self.video.is_error_message_shown) - - def test_captions_shown_correctly(self): - """ - Scenario: Captions are shown correctly - Given I have created a Video component with subtitles - Then when I view the video it does show the captions - """ - self._create_course_unit(subtitles=True) - self.assertTrue(self.video.is_captions_visible()) - - def test_captions_toggling(self): - """ - Scenario: Captions are toggled correctly - Given I have created a Video component with subtitles - And I have toggled captions - Then when I view the video it does show the captions - """ - self._create_course_unit(subtitles=True) - - self.video.click_player_button('transcript_button') - - self.assertFalse(self.video.is_captions_visible()) - - self.video.click_player_button('transcript_button') - - self.assertTrue(self.video.is_captions_visible()) - - def test_transcript_state_is_saved_on_reload(self): - """ - Scenario: Transcripts state is preserved - Given I have created a Video component with subtitles - And I have toggled off the transcript - After page reload transcript is already off - Then when I view the video it does show the captions - """ - self._create_course_unit(subtitles=True) - self.video.click_player_button('transcript_button') - self.assertFalse(self.video.is_captions_visible()) - self.video.click_player_button('transcript_button') - self.assertTrue(self.video.is_captions_visible()) - self.browser.refresh() - self.assertTrue(self.video.is_captions_visible()) - - def test_caption_line_focus(self): - """ - Scenario: When enter key is pressed on a caption, an outline shows around it - Given I have created a Video component with subtitles - And Make sure captions are opened - Then I focus on first caption line - And I see first caption line has focused - """ - self._create_course_unit(subtitles=True) - - self.video.show_captions() - - self.video.focus_caption_line(2) - - self.assertTrue(self.video.is_caption_line_focused(2)) - - def test_slider_range_works(self): - """ - Scenario: When start and end times are specified, a range on slider is shown - Given I have created a Video component with subtitles - And Make sure captions are closed - And I edit the component - And I open tab "Advanced" - And I set value "00:00:12" to the field "Video Start Time" - And I set value "00:00:24" to the field "Video Stop Time" - And I save changes - And I click video button "play" - Then I see a range on slider - """ - self._create_course_unit(subtitles=True) - - self.video.hide_captions() - - self.edit_component() - - self.open_advanced_tab() - - self.video.set_field_value('Video Start Time', '00:00:12') - - self.video.set_field_value('Video Stop Time', '00:00:24') - - self.save_unit_settings() - - self.video.click_player_button('play') - - -class CMSVideoA11yTest(CMSVideoBaseTest): - """ - CMS Video Accessibility Test Class - """ - a11y = True - - def setUp(self): - browser = os.environ.get('SELENIUM_BROWSER', 'firefox') - - # the a11y tests run in CI under phantomjs which doesn't - # support html5 video or flash player, so the video tests - # don't work in it. We still want to be able to run these - # tests in CI, so override the browser setting if it is - # phantomjs. - if browser == 'phantomjs': - browser = 'firefox' - - with patch.dict(os.environ, {'SELENIUM_BROWSER': browser}): - super(CMSVideoA11yTest, self).setUp() diff --git a/common/test/acceptance/tests/video/test_studio_video_transcript.py b/common/test/acceptance/tests/video/test_studio_video_transcript.py deleted file mode 100644 index 8beb18aa6c..0000000000 --- a/common/test/acceptance/tests/video/test_studio_video_transcript.py +++ /dev/null @@ -1,445 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -Acceptance tests for CMS Video Transcripts. - -For transcripts acceptance tests there are 3 available caption -files. They can be used to test various transcripts features. Two of -them can be imported from YouTube. - -The length of each file name is 11 characters. This is because the -YouTube's ID length is 11 characters. If file name is not of length 11, -front-end validation will not pass. - - t__eq_exist - this file exists on YouTube, and can be imported - via the transcripts menu; after import, this file will - be equal to the one stored locally - t_neq_exist - same as above, except local file will differ from the - one stored on YouTube - t_not_exist - this file does not exist on YouTube; it exists locally -""" - - -from common.test.acceptance.tests.video.test_studio_video_module import CMSVideoBaseTest - - -class VideoTranscriptTest(CMSVideoBaseTest): - """ - CMS Video Transcript Test Class - """ - shard = 18 - - def _create_video_component(self, subtitles=False, subtitle_id='3_yD_cEKoCk'): - """ - Create a video component and navigate to unit page - - Arguments: - subtitles (bool): Upload subtitles or not - subtitle_id (str): subtitle file id - - """ - if subtitles: - self.assets.append('subs_{}.srt.sjson'.format(subtitle_id)) - - self.navigate_to_course_unit() - - def test_youtube_id_w_different_local_server_sub(self): - """ - Scenario: Youtube id only: check "Found" state when user sets youtube_id with different local and server subs - Given I have created a Video component with subtitles "t_neq_exist" - - And I enter a "http://youtu.be/t_neq_exist" source to field number 1 - And I see status message "Timed Transcript Conflict" - And I see button "replace" - And I click transcript button "replace" - And I see status message "Timed Transcript Found" - Then I save video component And captions are visible. - """ - self._create_video_component(subtitles=True, subtitle_id='t_neq_exist') - self.edit_component() - - self.video.set_url_field('http://youtu.be/t_neq_exist', 1) - self.assertEqual(self.video.message('status'), 'Timed Transcript Conflict') - self.assertTrue(self.video.is_transcript_button_visible('replace')) - self.video.click_button_subtitles() - self.video.wait_for_message('status', 'Timed Transcript Found') - self.save_unit_settings() - self.assertTrue(self.video.is_captions_visible()) - - def test_html5_source_w_not_found_state(self): - """ - Scenario: html5 source only: check "Not Found" state - Given I have created a Video component - - And I enter a "t_not_exist.mp4" source to field number 1 - Then I see status message "No Timed Transcript" - """ - self._create_video_component() - self.edit_component() - - self.video.set_url_field('t_not_exist.mp4', 1) - self.assertEqual(self.video.message('status'), 'No Timed Transcript') - - def test_set_youtube_id_wo_local(self): - """ - Scenario: User sets youtube_id w/o local but with server subs and one html5 link w/o - transcripts w/o import action, then another one html5 link w/o transcripts - Given I have created a Video component - - urls = ['http://youtu.be/t__eq_exist', 't_not_exist.mp4', 't_not_exist.webm'] - for each url in urls do the following - Enter `url` to field number `n` - Status message `No EdX Timed Transcript` is shown - `import` and `upload_new_timed_transcripts` are shown - """ - self._create_video_component() - self.edit_component() - - urls = ['http://youtu.be/t__eq_exist', 't_not_exist.mp4', 't_not_exist.webm'] - for index, url in enumerate(urls, 1): - self.video.set_url_field(url, index) - self.assertEqual(self.video.message('status'), 'No EdX Timed Transcript') - self.assertTrue(self.video.is_transcript_button_visible('import')) - self.assertTrue(self.video.is_transcript_button_visible('upload_new_timed_transcripts')) - - def test_youtube_with_import(self): - """ - Scenario: Entering youtube with imported transcripts, and 2 html5 sources without transcripts - "Found" - Given I have created a Video component - - And I enter a "http://youtu.be/t__eq_exist" source to field number 1 - Then I see status message "No EdX Timed Transcript" - And I see button "import" - And I click transcript button "import" - Then I see status message "Timed Transcript Found" - And I see button "upload_new_timed_transcripts" - - urls = ['t_not_exist.mp4', 't_not_exist.webm'] - for each url in urls do the following - Enter `url` to field number `n` - Status message `Timed Transcript Found` is shown - `download_to_edit` and `upload_new_timed_transcripts` buttons are shown - """ - self._create_video_component() - self.edit_component() - - self.video.set_url_field('http://youtu.be/t__eq_exist', 1) - self.assertEqual(self.video.message('status'), 'No EdX Timed Transcript') - self.assertTrue(self.video.is_transcript_button_visible('import')) - self.video.click_button('import') - self.assertEqual(self.video.message('status'), 'Timed Transcript Found') - self.assertTrue(self.video.is_transcript_button_visible('upload_new_timed_transcripts')) - - urls = ['t_not_exist.mp4', 't_not_exist.webm'] - for index, url in enumerate(urls, 2): - self.video.set_url_field(url, index) - self.assertEqual(self.video.message('status'), 'Timed Transcript Found') - self.assertTrue(self.video.is_transcript_button_visible('download_to_edit')) - self.assertTrue(self.video.is_transcript_button_visible('upload_new_timed_transcripts')) - - def test_youtube_wo_imported_transcripts(self): - """ - Scenario: Entering youtube w/o imported transcripts - html5 w/o transcripts w/o import - html5 with transcripts - Given I have created a Video component with subtitles "t_neq_exist" - - urls = ['http://youtu.be/t__eq_exist', 't_not_exist.mp4', 't_neq_exist.webm'] - for each url in urls do the following - Enter `url` to field number `n` - Status message `No EdX Timed Transcript` is shown - `import` and `upload_new_timed_transcripts` buttons are shown - """ - self._create_video_component(subtitles=True, subtitle_id='t_neq_exist') - self.edit_component() - - urls = ['http://youtu.be/t__eq_exist', 't_not_exist.mp4', 't_neq_exist.webm'] - for index, url in enumerate(urls, 1): - self.video.set_url_field(url, index) - self.assertEqual(self.video.message('status'), 'No EdX Timed Transcript') - self.assertTrue(self.video.is_transcript_button_visible('import')) - self.assertTrue(self.video.is_transcript_button_visible('upload_new_timed_transcripts')) - - def test_youtube_w_imported_transcripts(self): - """ - Scenario: Entering youtube with imported transcripts - html5 with transcripts - html5 w/o transcripts - Given I have created a Video component with subtitles "t_neq_exist" - - And I enter a "http://youtu.be/t__eq_exist" source to field number 1 - Then I see status message "No EdX Timed Transcript" - And I see button "import" - And I click transcript button "import" - Then I see status message "Timed Transcript Found" - And I see button "upload_new_timed_transcripts" - - urls = ['t_neq_exist.mp4', 't_not_exist.webm'] - for each url in urls do the following - Enter `url` to field number `n` - Status message `Timed Transcript Found` is shown - `download_to_edit` and `upload_new_timed_transcripts` buttons are shown - """ - self._create_video_component(subtitles=True, subtitle_id='t_neq_exist') - self.edit_component() - - self.video.set_url_field('http://youtu.be/t__eq_exist', 1) - self.assertEqual(self.video.message('status'), 'No EdX Timed Transcript') - self.assertTrue(self.video.is_transcript_button_visible('import')) - self.video.click_button('import') - self.assertEqual(self.video.message('status'), 'Timed Transcript Found') - self.assertTrue(self.video.is_transcript_button_visible('upload_new_timed_transcripts')) - - urls = ['t_neq_exist.mp4', 't_not_exist.webm'] - for index, url in enumerate(urls, 2): - self.video.set_url_field(url, index) - self.assertEqual(self.video.message('status'), 'Timed Transcript Found') - self.assertTrue(self.video.is_transcript_button_visible('download_to_edit')) - self.assertTrue(self.video.is_transcript_button_visible('upload_new_timed_transcripts')) - - def test_youtube_w_imported_transcripts2(self): - """ - Scenario: Entering youtube with imported transcripts - html5 w/o transcripts - html5 with transcripts - Given I have created a Video component with subtitles "t_neq_exist" - - And I enter a "http://youtu.be/t__eq_exist" source to field number 1 - Then I see status message "No EdX Timed Transcript" - And I see button "import" - And I click transcript button "import" - Then I see status message "Timed Transcript Found" - And I see button "upload_new_timed_transcripts" - - urls = ['t_not_exist.mp4', 't_neq_exist.webm'] - for each url in urls do the following - Enter `url` to field number `n` - Status message `Timed Transcript Found` is shown - `download_to_edit` and `upload_new_timed_transcripts` buttons are shown - """ - self._create_video_component(subtitles=True, subtitle_id='t_neq_exist') - self.edit_component() - - self.video.set_url_field('http://youtu.be/t__eq_exist', 1) - self.assertEqual(self.video.message('status'), 'No EdX Timed Transcript') - self.assertTrue(self.video.is_transcript_button_visible('import')) - self.video.click_button('import') - self.assertEqual(self.video.message('status'), 'Timed Transcript Found') - self.assertTrue(self.video.is_transcript_button_visible('upload_new_timed_transcripts')) - - urls = ['t_not_exist.mp4', 't_neq_exist.webm'] - for index, url in enumerate(urls, 2): - self.video.set_url_field(url, index) - self.assertEqual(self.video.message('status'), 'Timed Transcript Found') - self.assertTrue(self.video.is_transcript_button_visible('download_to_edit')) - self.assertTrue(self.video.is_transcript_button_visible('upload_new_timed_transcripts')) - - def test_html5_with_transcripts(self): - """ - Scenario: Entering html5 with transcripts - upload - youtube w/o transcripts - Given I have created a Video component with subtitles "t__eq_exist" - - And I enter a "t__eq_exist.mp4" source to field number 1 - Then I see status message "Timed Transcript Found" - `download_to_edit` and `upload_new_timed_transcripts` buttons are shown - And I upload the transcripts file "uk_transcripts.srt" - Then I see status message "Timed Transcript Uploaded Successfully" - `download_to_edit` and `upload_new_timed_transcripts` buttons are shown - - And I enter a "http://youtu.be/t_not_exist" source to field number 2 - Then I see status message "Timed Transcript Found" - `download_to_edit` and `upload_new_timed_transcripts` buttons are shown - - And I enter a "uk_transcripts.webm" source to field number 3 - Then I see status message "Timed Transcript Found" - """ - self._create_video_component(subtitles=True, subtitle_id='t__eq_exist') - self.edit_component() - - self.video.set_url_field('t__eq_exist.mp4', 1) - self.assertEqual(self.video.message('status'), 'Timed Transcript Found') - self.assertTrue(self.video.is_transcript_button_visible('download_to_edit')) - self.assertTrue(self.video.is_transcript_button_visible('upload_new_timed_transcripts')) - self.video.upload_transcript('uk_transcripts.srt') - self.assertEqual(self.video.message('status'), 'Timed Transcript Uploaded Successfully') - self.assertTrue(self.video.is_transcript_button_visible('download_to_edit')) - self.assertTrue(self.video.is_transcript_button_visible('upload_new_timed_transcripts')) - - self.video.set_url_field('http://youtu.be/t_not_exist', 2) - self.assertEqual(self.video.message('status'), 'Timed Transcript Found') - self.assertTrue(self.video.is_transcript_button_visible('download_to_edit')) - self.assertTrue(self.video.is_transcript_button_visible('upload_new_timed_transcripts')) - - self.video.set_url_field('uk_transcripts.webm', 3) - self.assertEqual(self.video.message('status'), 'Timed Transcript Found') - - def test_two_fields_only(self): - """ - Scenario: Work with 2 fields: Enter HTML5 source with transcripts - save -> change it to another one HTML5 - source w/o transcripts - do not click on use existing -> add another one HTML5 source w/o - transcripts - click on use existing - Given I have created a Video component with subtitles "t_not_exist" - - And I enter a "t_not_exist.mp4" source to field number 1 - Then I see status message "Timed Transcript Found" - `download_to_edit` and `upload_new_timed_transcripts` buttons are shown - And I save changes - And I edit the component - - And I enter a "video_name_2.mp4" source to field number 1 - Then I see status message "Confirm Timed Transcript" - And I see button "use_existing" - - And I enter a "video_name_3.webm" source to field number 2 - Then I see status message "Confirm Timed Transcript" - And I see button "use_existing" - And I click transcript button "use_existing" - And I see status message "Timed Transcript Found" - - I save video component And see that the captions are visible - Then I edit video component And I see status message "Timed Transcript Found" - """ - self.metadata = {'sub': 't_not_exist'} - self._create_video_component(subtitles=True, subtitle_id='t_not_exist') - self.edit_component() - - self.video.set_url_field('t_not_exist.mp4', 1) - self.assertEqual(self.video.message('status'), 'Timed Transcript Found') - self.assertTrue(self.video.is_transcript_button_visible('download_to_edit')) - self.assertTrue(self.video.is_transcript_button_visible('upload_new_timed_transcripts')) - self.save_unit_settings() - self.edit_component() - - self.video.set_url_field('video_name_2.mp4', 1) - self.assertEqual(self.video.message('status'), 'Confirm Timed Transcript') - self.assertTrue(self.video.is_transcript_button_visible('use_existing')) - - self.video.set_url_field('video_name_3.webm', 2) - self.assertEqual(self.video.message('status'), 'Confirm Timed Transcript') - self.assertTrue(self.video.is_transcript_button_visible('use_existing')) - self.video.click_button('use_existing') - self.assertEqual(self.video.message('status'), 'Timed Transcript Found') - - self.save_unit_settings() - self.video.is_captions_visible() - - self.edit_component() - self.assertEqual(self.video.message('status'), 'Timed Transcript Found') - - def test_video_wo_subtitles(self): - """ - Scenario: Video w/o subs - another video w/o subs - Not found message - Video can have filled item.sub, but doesn't have subs file. - In this case, after changing this video by another one without subs - `No Timed Transcript` message should appear ( not 'Confirm Timed Transcript'). - Given I have created a Video component - - And I enter a "video_name_1.mp4" source to field number 1 - Then I see status message "No Timed Transcript" - """ - self._create_video_component() - self.edit_component() - - self.video.set_url_field('video_name_1.mp4', 1) - self.assertEqual(self.video.message('status'), 'No Timed Transcript') - - def test_upload_button_w_youtube(self): - """ - Scenario: Upload button for single youtube id - Given I have created a Video component - - After I enter a "http://youtu.be/t_not_exist" source to field number 1 I see message "No Timed Transcript" - And I see button "upload_new_timed_transcripts" - After I upload the transcripts file "uk_transcripts.srt" I see message "Timed Transcript Uploaded Successfully" - After saving the changes video captions should be visible - When I edit the component Then I see status message "Timed Transcript Found" - """ - self._create_video_component() - self.edit_component() - - self.video.set_url_field('http://youtu.be/t_not_exist', 1) - self.assertEqual(self.video.message('status'), 'No Timed Transcript') - self.assertTrue(self.video.is_transcript_button_visible('upload_new_timed_transcripts')) - self.video.upload_transcript('uk_transcripts.srt') - self.assertEqual(self.video.message('status'), 'Timed Transcript Uploaded Successfully') - self.save_unit_settings() - self.assertTrue(self.video.is_captions_visible()) - - self.edit_component() - self.assertEqual(self.video.message('status'), 'Timed Transcript Found') - - def test_upload_button_w_html5_ids(self): - """ - Scenario: Upload button for youtube id with html5 ids - Given I have created a Video component - - After I enter a "http://youtu.be/t_not_exist" source to field number 1 I see message "No Timed Transcript" - And I see button "upload_new_timed_transcripts" - - After I enter a "video_name_1.mp4" source to field number 2 Then I see status message "No Timed Transcript" - And I see button "upload_new_timed_transcripts" - After I upload the transcripts file "uk_transcripts.srt"I see message "Timed Transcript Uploaded Successfully" - When I clear field number 1 Then I see status message "Timed Transcript Found" - After saving the changes video captions are visible - When I edit the component Then I see status message "Timed Transcript Found" - """ - self._create_video_component() - self.edit_component() - - self.video.set_url_field('http://youtu.be/t_not_exist', 1) - self.assertEqual(self.video.message('status'), 'No Timed Transcript') - self.assertTrue(self.video.is_transcript_button_visible('upload_new_timed_transcripts')) - - self.video.set_url_field('video_name_1.mp4', 2) - self.assertEqual(self.video.message('status'), 'No Timed Transcript') - self.assertTrue(self.video.is_transcript_button_visible('upload_new_timed_transcripts')) - self.video.upload_transcript('uk_transcripts.srt') - self.assertEqual(self.video.message('status'), 'Timed Transcript Uploaded Successfully') - # Removing a source from "Video URL" field will make an ajax call to `check_transcripts`. - self.video.clear_field(1) - self.assertEqual(self.video.message('status'), 'Timed Transcript Found') - self.save_unit_settings() - self.assertTrue(self.video.is_captions_visible()) - - self.edit_component() - self.assertEqual(self.video.message('status'), 'Timed Transcript Found') - - def test_upload_subtitles_w_different_names3(self): - """ - Scenario: Shortened link: Shortened link to the source does not effect the uploaded - transcript, given I have created a Video component - - After I enter a "http://goo.gl/pxxZrg" source to field number 1 Then I see status message "No Timed Transcript" - After I upload the transcripts file "uk_transcripts.srt" I see message "Timed Transcript Uploaded Successfully" - After saving the changes video captions should be visible - After I edit the component I should see status message "Timed Transcript Found" - """ - self._create_video_component() - self.edit_component() - - self.video.set_url_field('http://goo.gl/pxxZrg', 1) - self.assertEqual(self.video.message('status'), 'No Timed Transcript') - self.video.upload_transcript('uk_transcripts.srt') - self.assertEqual(self.video.message('status'), 'Timed Transcript Uploaded Successfully') - self.save_unit_settings() - self.assertTrue(self.video.is_captions_visible()) - - self.edit_component() - self.assertEqual(self.video.message('status'), 'Timed Transcript Found') - - def test_upload_subtitles_w_different_names4(self): - """ - Scenario: Relative link: Relative link to the source does not effect the uploaded - transcript, given I have created a Video component - - After i enter a "/gizmo.webm" source to field number 1 Then I see status message "No Timed Transcript" - After I upload the transcripts file "uk_transcripts.srt" I see message "Timed Transcript Uploaded Successfully" - After saving the changes video captions should be visible - After I edit the component I should see status message "Timed Transcript Found" - """ - self._create_video_component() - self.edit_component() - - self.video.set_url_field('/gizmo.webm', 1) - self.assertEqual(self.video.message('status'), 'No Timed Transcript') - self.video.upload_transcript('uk_transcripts.srt') - self.assertEqual(self.video.message('status'), 'Timed Transcript Uploaded Successfully') - self.save_unit_settings() - self.assertTrue(self.video.is_captions_visible()) - - self.edit_component() - self.assertEqual(self.video.message('status'), 'Timed Transcript Found') diff --git a/common/test/acceptance/tests/video/test_video_events.py b/common/test/acceptance/tests/video/test_video_events.py deleted file mode 100644 index 1a1b21d9b6..0000000000 --- a/common/test/acceptance/tests/video/test_video_events.py +++ /dev/null @@ -1,189 +0,0 @@ -"""Ensure videos emit proper events""" - - -import datetime -import json - -import six -from opaque_keys.edx.keys import CourseKey, UsageKey - -from common.test.acceptance.tests.helpers import EventsTestMixin -from common.test.acceptance.tests.video.test_video_module import VideoBaseTest -from openedx.core.lib.tests.assertions.events import assert_event_matches, assert_events_equal - - -class VideoEventsTestMixin(EventsTestMixin, VideoBaseTest): - """ - Useful helper methods to test video player event emission. - """ - def assert_payload_contains_ids(self, video_event): - """ - Video events should all contain "id" and "code" attributes in their payload. - - This function asserts that those fields are present and have correct values. - """ - video_descriptors = self.course_fixture.get_nested_xblocks(category='video') - video_desc = video_descriptors[0] - video_locator = UsageKey.from_string(video_desc.locator) - - expected_event = { - 'event': { - 'id': video_locator.html_id(), - 'code': '3_yD_cEKoCk' - } - } - self.assert_events_match([expected_event], [video_event]) - - def assert_valid_control_event_at_time(self, video_event, time_in_seconds): - """ - Video control events should contain valid ID fields and a valid "currentTime" field. - - This function asserts that those fields are present and have correct values. - """ - current_time = json.loads(video_event['event'])['currentTime'] - self.assertAlmostEqual(current_time, time_in_seconds, delta=1) - - def assert_field_type(self, event_dict, field, field_type): - """Assert that a particular `field` in the `event_dict` has a particular type""" - self.assertIn(field, event_dict, u'{0} not found in the root of the event'.format(field)) - self.assertTrue( - isinstance(event_dict[field], field_type), - u'Expected "{key}" to be a "{field_type}", but it has the value "{value}" of type "{t}"'.format( - key=field, - value=event_dict[field], - t=type(event_dict[field]), - field_type=field_type, - ) - ) - - -class VideoEventsTest(VideoEventsTestMixin): - """ Test video player event emission """ - shard = 21 - - def test_video_control_events(self): - """ - Scenario: Video component is rendered in the LMS in Youtube mode without HTML5 sources - Given the course has a Video component in "Youtube" mode - And I play the video - And I watch 5 seconds of it - And I pause the video - Then a "load_video" event is emitted - And a "play_video" event is emitted - And a "pause_video" event is emitted - """ - - def is_video_event(event): - """Filter out anything other than the video events of interest""" - return event['event_type'] in ('load_video', 'play_video', 'pause_video') - - captured_events = [] - with self.capture_events(is_video_event, number_of_matches=3, captured_events=captured_events): - self.navigate_to_video() - self.video.click_player_button('play') - self.video.wait_for_position('0:05') - self.video.click_player_button('pause') - - for idx, video_event in enumerate(captured_events): - self.assert_payload_contains_ids(video_event) - if idx == 0: - assert_event_matches({'event_type': 'load_video'}, video_event) - elif idx == 1: - assert_event_matches({'event_type': 'play_video'}, video_event) - self.assert_valid_control_event_at_time(video_event, 0) - elif idx == 2: - assert_event_matches({'event_type': 'pause_video'}, video_event) - self.assert_valid_control_event_at_time(video_event, self.video.seconds) - - def test_strict_event_format(self): - """ - This test makes a very strong assertion about the fields present in events. The goal of it is to ensure that new - fields are not added to all events mistakenly. It should be the only existing test that is updated when new top - level fields are added to all events. - """ - - captured_events = [] - with self.capture_events(lambda e: e['event_type'] == 'load_video', captured_events=captured_events): - self.navigate_to_video() - - load_video_event = captured_events[0] - - # Validate the event payload - self.assert_payload_contains_ids(load_video_event) - - # We cannot predict the value of these fields so we make weaker assertions about them - dynamic_string_fields = ( - 'accept_language', - 'agent', - 'host', - 'ip', - 'event', - 'session' - ) - for field in dynamic_string_fields: - self.assert_field_type(load_video_event, field, six.string_types) - self.assertIn(field, load_video_event, u'{0} not found in the root of the event'.format(field)) - del load_video_event[field] - - # A weak assertion for the timestamp as well - self.assert_field_type(load_video_event, 'time', datetime.datetime) - del load_video_event['time'] - - # Note that all unpredictable fields have been deleted from the event at this point - - course_key = CourseKey.from_string(self.course_id) - static_fields_pattern = { - 'context': { - 'course_id': six.text_type(course_key), - 'org_id': course_key.org, - 'path': '/event', - 'user_id': self.user_info['user_id'] - }, - 'event_source': 'browser', - 'event_type': 'load_video', - 'username': self.user_info['username'], - 'page': self.browser.current_url, - 'referer': self.browser.current_url, - 'name': 'load_video', - } - assert_events_equal(static_fields_pattern, load_video_event) - - -class VideoHLSEventsTest(VideoEventsTestMixin): - """ - Test video player event emission for HLS video - """ - shard = 19 - - def test_event_data_for_hls(self): - """ - Scenario: Video component with HLS video emits events correctly - - Given the course has a Video component with Youtube, HTML5 and HLS sources available. - And I play the video - And the video starts playing - And I watch 3 seconds of it - When I pause and seek the video - And I play the video to the end - Then I verify that all expected events are triggered - And triggered events have correct data - """ - video_events = ('load_video', 'play_video', 'pause_video', 'seek_video') - - def is_video_event(event): - """ - Filter out anything other than the video events of interest - """ - return event['event_type'] in video_events - - captured_events = [] - with self.capture_events(is_video_event, captured_events=captured_events): - self.metadata = self.metadata_for_mode('hls') - self.navigate_to_video() - self.video.click_player_button('play') - self.video.wait_for_position('0:03') - self.video.click_player_button('pause') - self.video.seek('0:08') - - expected_events = [{'name': event, 'event': {'code': 'hls'}} for event in video_events] - self.assert_events_match(expected_events, captured_events) diff --git a/common/test/acceptance/tests/video/test_video_module.py b/common/test/acceptance/tests/video/test_video_module.py index d868201527..e0d889ad53 100644 --- a/common/test/acceptance/tests/video/test_video_module.py +++ b/common/test/acceptance/tests/video/test_video_module.py @@ -7,14 +7,10 @@ Acceptance tests for Video. import os from unittest import skipIf - -from ddt import data, ddt, unpack from mock import patch -from six.moves import range from common.test.acceptance.fixtures.course import CourseFixture, XBlockFixtureDesc from common.test.acceptance.pages.common.auto_auth import AutoAuthPage -from common.test.acceptance.pages.lms.course_info import CourseInfoPage from common.test.acceptance.pages.lms.courseware import CoursewarePage from common.test.acceptance.pages.lms.tab_nav import TabNavPage from common.test.acceptance.pages.lms.video.video import VideoPage @@ -22,7 +18,6 @@ from common.test.acceptance.tests.helpers import ( UniqueCourseTest, YouTubeStubConfig, is_youtube_available, - skip_if_browser ) from openedx.core.lib.tests import attr @@ -61,7 +56,6 @@ class VideoBaseTest(UniqueCourseTest): self.video = VideoPage(self.browser) self.tab_nav = TabNavPage(self.browser) self.courseware_page = CoursewarePage(self.browser, self.course_id) - self.course_info_page = CourseInfoPage(self.browser, self.course_id) self.auth_page = AutoAuthPage(self.browser, course_id=self.course_id) self.course_fixture = CourseFixture( @@ -217,727 +211,6 @@ class VideoBaseTest(UniqueCourseTest): self.video.wait_for_video_player_render() -@attr(shard=13) -@ddt -class YouTubeVideoTest(VideoBaseTest): - """ Test YouTube Video Player """ - - def test_youtube_video_rendering_wo_html5_sources(self): - """ - Scenario: Video component is rendered in the LMS in Youtube mode without HTML5 sources - Given the course has a Video component in "Youtube" mode - Then the video has rendered in "Youtube" mode - """ - self.navigate_to_video() - - # Verify that video has rendered in "Youtube" mode - self.assertTrue(self.video.is_video_rendered('youtube')) - - def test_transcript_button_wo_english_transcript(self): - """ - Scenario: Transcript button works correctly w/o english transcript in Youtube mode - Given the course has a Video component in "Youtube" mode - And I have defined a non-english transcript for the video - And I have uploaded a non-english transcript file to assets - Then I see the correct text in the captions - """ - data = {'transcripts': {'zh': 'chinese_transcripts.srt'}} - self.metadata = self.metadata_for_mode('youtube', data) - self.assets.append('chinese_transcripts.srt') - self.navigate_to_video() - self.video.show_captions() - - # Verify that we see "好 各位同学" text in the transcript - unicode_text = u"好 各位同学" - self.assertIn(unicode_text, self.video.captions_text) - - def test_cc_button(self): - """ - Scenario: CC button works correctly with transcript in YouTube mode - Given the course has a video component in "Youtube" mode - And I have defined a transcript for the video - Then I see the closed captioning element over the video - """ - data = {'transcripts': {'zh': 'chinese_transcripts.srt'}} - self.metadata = self.metadata_for_mode('youtube', data) - self.assets.append('chinese_transcripts.srt') - self.navigate_to_video() - - # Show captions and make sure they're visible and cookie is set - self.video.show_closed_captions() - self.video.wait_for_closed_captions() - self.assertTrue(self.video.is_closed_captions_visible) - self.video.reload_page() - self.assertTrue(self.video.is_closed_captions_visible) - - # Hide captions and make sure they're hidden and cookie is unset - self.video.hide_closed_captions() - self.video.wait_for_closed_captions_to_be_hidden() - self.video.reload_page() - self.video.wait_for_closed_captions_to_be_hidden() - - def test_transcript_button_transcripts_and_sub_fields_empty(self): - """ - Scenario: Transcript button works correctly if transcripts and sub fields are empty, - but transcript file exists in assets (Youtube mode of Video component) - Given the course has a Video component in "Youtube" mode - And I have uploaded a .srt.sjson file to assets - Then I see the correct english text in the captions - """ - self._install_course_fixture() - self.course_fixture.add_asset(['subs_3_yD_cEKoCk.srt.sjson']) - self.course_fixture._upload_assets() - self._navigate_to_courseware_video_and_render() - self.video.show_captions() - - # Verify that we see "Welcome to edX." text in the captions - self.assertIn('Welcome to edX.', self.video.captions_text) - - def test_transcript_button_hidden_no_translations(self): - """ - Scenario: Transcript button is hidden if no translations - Given the course has a Video component in "Youtube" mode - Then the "Transcript" button is hidden - """ - self.navigate_to_video() - self.assertFalse(self.video.is_button_shown('transcript_button')) - - def test_download_button_wo_english_transcript(self): - """ - Scenario: Download button works correctly w/o english transcript in YouTube mode - Given the course has a Video component in "Youtube" mode - And I have defined a downloadable non-english transcript for the video - And I have uploaded a non-english transcript file to assets - Then I can download the transcript in "srt" format - """ - data = {'download_track': True, 'transcripts': {'zh': 'chinese_transcripts.srt'}} - self.metadata = self.metadata_for_mode('youtube', additional_data=data) - self.assets.append('chinese_transcripts.srt') - - # go to video - self.navigate_to_video() - - # check if we can download transcript in "srt" format that has text "好 各位同学" - unicode_text = u"好 各位同学" - self.assertTrue(self.video.downloaded_transcript_contains_text('srt', unicode_text)) - - def test_download_button_two_transcript_languages(self): - """ - Scenario: Download button works correctly for multiple transcript languages - Given the course has a Video component in "Youtube" mode - And I have defined a downloadable non-english transcript for the video - And I have defined english subtitles for the video - Then I see the correct english text in the captions - And the english transcript downloads correctly - And I see the correct non-english text in the captions - And the non-english transcript downloads correctly - """ - self.assets.extend(['chinese_transcripts.srt', 'subs_3_yD_cEKoCk.srt.sjson']) - data = {'download_track': True, 'transcripts': {'zh': 'chinese_transcripts.srt'}, 'sub': '3_yD_cEKoCk'} - self.metadata = self.metadata_for_mode('youtube', additional_data=data) - - # go to video - self.navigate_to_video() - - # check if "Welcome to edX." text in the captions - self.assertIn('Welcome to edX.', self.video.captions_text) - - # check if we can download transcript in "srt" format that has text "Welcome to edX." - self.assertTrue(self.video.downloaded_transcript_contains_text('srt', 'Welcome to edX.')) - - # select language with code "zh" - self.assertTrue(self.video.select_language('zh')) - - # check if we see "好 各位同学" text in the captions - unicode_text = u"好 各位同学" - self.assertIn(unicode_text, self.video.captions_text) - - # check if we can download transcript in "srt" format that has text "好 各位同学" - unicode_text = u"好 各位同学" - self.assertTrue(self.video.downloaded_transcript_contains_text('srt', unicode_text)) - - def test_video_rendering_with_default_response_time(self): - """ - Scenario: Video is rendered in Youtube mode when the YouTube Server responds quickly - Given the YouTube server response time less than 1.5 seconds - And the course has a Video component in "Youtube_HTML5" mode - Then the video has rendered in "Youtube" mode - """ - # configure youtube server - self.youtube_configuration['time_to_response'] = 0.4 - self.metadata = self.metadata_for_mode('youtube_html5') - - self.navigate_to_video() - - self.assertTrue(self.video.is_video_rendered('youtube')) - - def test_video_rendering_wo_default_response_time(self): - """ - Scenario: Video is rendered in HTML5 when the YouTube Server responds slowly - Given the YouTube server response time is greater than 1.5 seconds - And the course has a Video component in "Youtube_HTML5" mode - Then the video has rendered in "HTML5" mode - """ - # configure youtube server - self.youtube_configuration['time_to_response'] = 7.0 - self.metadata = self.metadata_for_mode('youtube_html5') - - self.navigate_to_video() - - self.assertTrue(self.video.is_video_rendered('html5')) - - def test_video_with_youtube_blocked_with_default_response_time(self): - """ - Scenario: Video is rendered in HTML5 mode when the YouTube API is blocked - Given the YouTube API is blocked - And the course has a Video component in "Youtube_HTML5" mode - Then the video has rendered in "HTML5" mode - And only one video has rendered - """ - # configure youtube server - self.youtube_configuration.update({ - 'youtube_api_blocked': True, - }) - - self.metadata = self.metadata_for_mode('youtube_html5') - - self.navigate_to_video() - - self.assertTrue(self.video.is_video_rendered('html5')) - - # The video should only be loaded once - self.assertEqual(len(self.video.q(css='video')), 1) - - def test_video_with_youtube_blocked_delayed_response_time(self): - """ - Scenario: Video is rendered in HTML5 mode when the YouTube API is blocked - Given the YouTube server response time is greater than 1.5 seconds - And the YouTube API is blocked - And the course has a Video component in "Youtube_HTML5" mode - Then the video has rendered in "HTML5" mode - And only one video has rendered - """ - # configure youtube server - self.youtube_configuration.update({ - 'time_to_response': 2.0, - 'youtube_api_blocked': True, - }) - - self.metadata = self.metadata_for_mode('youtube_html5') - - self.navigate_to_video() - - self.assertTrue(self.video.is_video_rendered('html5')) - - # The video should only be loaded once - self.assertEqual(len(self.video.q(css='video')), 1) - - def test_html5_video_rendered_with_youtube_captions(self): - """ - Scenario: User should see Youtube captions for If there are no transcripts - available for HTML5 mode - Given that I have uploaded a .srt.sjson file to assets for Youtube mode - And the YouTube API is blocked - And the course has a Video component in "Youtube_HTML5" mode - And Video component rendered in HTML5 mode - And Html5 mode video has no transcripts - When I see the captions for HTML5 mode video - Then I should see the Youtube captions - """ - self.assets.append('subs_3_yD_cEKoCk.srt.sjson') - # configure youtube server - self.youtube_configuration.update({ - 'time_to_response': 2.0, - 'youtube_api_blocked': True, - }) - - data = {'sub': '3_yD_cEKoCk'} - self.metadata = self.metadata_for_mode('youtube_html5', additional_data=data) - - self.navigate_to_video() - - self.assertTrue(self.video.is_video_rendered('html5')) - # check if caption button is visible - self.assertTrue(self.video.is_button_shown('transcript_button')) - self._verify_caption_text('Welcome to edX.') - - @data(('srt', '00:00:00,260'), ('txt', 'Welcome to edX.')) - @unpack - def test_download_transcript_links_work_correctly(self, file_type, search_text): - """ - Scenario: Download 'srt' transcript link works correctly. - Download 'txt' transcript link works correctly. - Given the course has Video components A and B in "Youtube" mode - And Video component C in "HTML5" mode - And I have defined downloadable transcripts for the videos - Then I can download a transcript for Video A in "srt" format - And the Download Transcript menu does not exist for Video C - """ - - data_a = {'sub': '3_yD_cEKoCk', 'download_track': True} - youtube_a_metadata = self.metadata_for_mode('youtube', additional_data=data_a) - self.assets.append('subs_3_yD_cEKoCk.srt.sjson') - - data_b = {'youtube_id_1_0': 'b7xgknqkQk8', 'sub': 'b7xgknqkQk8', 'download_track': True} - youtube_b_metadata = self.metadata_for_mode('youtube', additional_data=data_b) - self.assets.append('subs_b7xgknqkQk8.srt.sjson') - - data_c = {'track': 'http://example.org/', 'download_track': True} - html5_c_metadata = self.metadata_for_mode('html5', additional_data=data_c) - - self.contents_of_verticals = [ - [{'display_name': 'A', 'metadata': youtube_a_metadata}], - [{'display_name': 'B', 'metadata': youtube_b_metadata}], - [{'display_name': 'C', 'metadata': html5_c_metadata}] - ] - - # open the section with videos (open vertical containing video "A") - self.navigate_to_video() - - # check if we can download transcript in "srt" format that has text "00:00:00,260" - self.assertTrue(self.video.downloaded_transcript_contains_text(file_type, search_text)) - - # open vertical containing video "C" - self.courseware_page.nav.go_to_vertical('Test Vertical-2') - - # menu "download_transcript" doesn't exist - self.assertFalse(self.video.is_menu_present('download_transcript')) - - def _verify_caption_text(self, text): - self.video._wait_for( - lambda: (text in self.video.captions_text), - u'Captions contain "{}" text'.format(text), - timeout=5 - ) - - def _verify_closed_caption_text(self, text): - """ - Scenario: returns True if the captions are visible, False is else - """ - self.video.wait_for( - lambda: (text in self.video.closed_captions_text), - u'Closed captions contain "{}" text'.format(text), - timeout=5 - ) - - def test_video_language_menu_working_closed_captions(self): - """ - Scenario: Language menu works correctly in Video component, checks closed captions - Given the course has a Video component in "Youtube" mode - And I have defined multiple language transcripts for the videos - And I make sure captions are closed - And I see video menu "language" with correct items - And I select language with code "en" - Then I see "Welcome to edX." text in the closed captions - And I select language with code "zh" - Then I see "我们今天要讲的题目是" text in the closed captions - """ - self.assets.extend(['chinese_transcripts.srt', 'subs_3_yD_cEKoCk.srt.sjson']) - data = {'transcripts': {"zh": "chinese_transcripts.srt"}, 'sub': '3_yD_cEKoCk'} - self.metadata = self.metadata_for_mode('youtube', additional_data=data) - - # go to video - self.navigate_to_video() - self.video.show_closed_captions() - - correct_languages = {'en': 'English', 'zh': 'Chinese'} - self.assertEqual(self.video.caption_languages, correct_languages) - - # we start the video, then pause it to activate the transcript - self.video.click_player_button('play') - self.video.wait_for_position('0:03') - self.video.click_player_button('pause') - - self.video.select_language('en') - self.video.click_transcript_line(line_no=1) - self._verify_closed_caption_text('Welcome to edX.') - - self.video.select_language('zh') - unicode_text = u"我们今天要讲的题目是" - self.video.click_transcript_line(line_no=1) - self._verify_closed_caption_text(unicode_text) - - def test_video_component_stores_speed_correctly_for_multiple_videos(self): - """ - Scenario: Video component stores speed correctly when each video is in separate sequential - Given I have a video "A" in "Youtube" mode in position "1" of sequential - And a video "B" in "Youtube" mode in position "2" of sequential - And a video "C" in "HTML5" mode in position "3" of sequential - """ - # vertical titles are created in VideoBaseTest._create_single_vertical - # and are of the form Test Vertical-{_} where _ is the index in self.contents_of_verticals - self.contents_of_verticals = [ - [{'display_name': 'A'}], [{'display_name': 'B'}], - [{'display_name': 'C', 'metadata': self.metadata_for_mode('html5')}] - ] - - self.navigate_to_video() - - # select the "2.0" speed on video "A" - self.courseware_page.nav.go_to_vertical('Test Vertical-0') - self.video.wait_for_video_player_render() - self.video.speed = '2.0' - - # select the "0.50" speed on video "B" - self.courseware_page.nav.go_to_vertical('Test Vertical-1') - self.video.wait_for_video_player_render() - self.video.speed = '0.50' - - # open video "C" - self.courseware_page.nav.go_to_vertical('Test Vertical-2') - self.video.wait_for_video_player_render() - - # Since the playback speed was set to .5 in "B", this video will also be impacted - # because a playback speed has never explicitly been set for it. However, this video - # does not have a .5 playback option, so the closest possible (.75) should be selected. - self.video.verify_speed_changed('0.75x') - - # go to the vertical containing video "A" - self.courseware_page.nav.go_to_vertical('Test Vertical-0') - - # Video "A" should still play at speed 2.0 because it was explicitly set to that. - self.assertEqual(self.video.speed, '2.0x') - - # reload the page - self.video.reload_page() - - # go to the vertical containing video "A" - self.courseware_page.nav.go_to_vertical('Test Vertical-0') - - # check if video "A" should start playing at speed "2.0" - self.assertEqual(self.video.speed, '2.0x') - - # select the "1.0" speed on video "A" - self.video.speed = '1.0' - - # go to the vertical containing "B" - self.courseware_page.nav.go_to_vertical('Test Vertical-1') - - # Video "B" should still play at speed .5 because it was explicitly set to that. - self.assertEqual(self.video.speed, '0.50x') - - # go to the vertical containing video "C" - self.courseware_page.nav.go_to_vertical('Test Vertical-2') - - # The change of speed for Video "A" should impact Video "C" because it still has - # not been explicitly set to a speed. - self.video.verify_speed_changed('1.0x') - - def test_video_has_correct_transcript(self): - """ - Scenario: Youtube video has correct transcript if fields for other speeds are filled - Given it has a video in "Youtube" mode - And I have uploaded multiple transcripts - And I make sure captions are opened - Then I see "Welcome to edX." text in the captions - And I select the "1.50" speed - And I reload the page with video - Then I see "Welcome to edX." text in the captions - And I see duration "1:56" - - """ - self.assets.extend(['subs_3_yD_cEKoCk.srt.sjson', 'subs_b7xgknqkQk8.srt.sjson']) - data = {'sub': '3_yD_cEKoCk', 'youtube_id_1_5': 'b7xgknqkQk8'} - self.metadata = self.metadata_for_mode('youtube', additional_data=data) - - # go to video - self.navigate_to_video() - - self.video.show_captions() - - self.assertIn('Welcome to edX.', self.video.captions_text) - - self.video.speed = '1.50' - - self.video.reload_page() - - self.assertIn('Welcome to edX.', self.video.captions_text) - - self.assertTrue(self.video.duration, '1.56') - - def test_video_position_stored_correctly_wo_seek(self): - """ - Scenario: Video component stores position correctly when page is reloaded - Given the course has a Video component in "Youtube" mode - Then the video has rendered in "Youtube" mode - And I click video button "play"" - Then I wait until video reaches at position "0.03" - And I click video button "pause" - And I reload the page with video - And I click video button "play"" - And I click video button "pause" - Then video slider should be Equal or Greater than "0:03" - - """ - self.navigate_to_video() - - self.video.click_player_button('play') - - self.video.wait_for_position('0:03') - - self.video.click_player_button('pause') - - self.video.reload_page() - - self.video.click_player_button('play') - self.video.click_player_button('pause') - - self.assertGreaterEqual(self.video.seconds, 3) - - def test_simplified_and_traditional_chinese_transcripts(self): - """ - Scenario: Simplified and Traditional Chinese transcripts work as expected in Youtube mode - - Given the course has a Video component in "Youtube" mode - And I have defined a Simplified Chinese transcript for the video - And I have defined a Traditional Chinese transcript for the video - Then I see the correct subtitle language options in cc menu - Then I see the correct text in the captions for Simplified and Traditional Chinese transcripts - And I can download the transcripts for Simplified and Traditional Chinese - And video subtitle menu has 'zh_HANS', 'zh_HANT' translations for 'Simplified Chinese' - and 'Traditional Chinese' respectively - """ - data = { - 'download_track': True, - 'transcripts': {'zh_HANS': 'simplified_chinese.srt', 'zh_HANT': 'traditional_chinese.srt'} - } - self.metadata = self.metadata_for_mode('youtube', data) - self.assets.extend(['simplified_chinese.srt', 'traditional_chinese.srt']) - self.navigate_to_video() - - langs = {'zh_HANS': u'在线学习是革', 'zh_HANT': u'在線學習是革'} - for lang_code, unicode_text in langs.items(): - self.video.scroll_to_button("transcript_button") - self.assertTrue(self.video.select_language(lang_code)) - self.assertIn(unicode_text, self.video.captions_text) - self.assertTrue(self.video.downloaded_transcript_contains_text('srt', unicode_text)) - - self.assertEqual(self.video.caption_languages, {'zh_HANS': 'Simplified Chinese', 'zh_HANT': 'Traditional Chinese'}) - - -@attr(shard=13) -class YouTubeHtml5VideoTest(VideoBaseTest): - """ Test YouTube HTML5 Video Player """ - - def test_youtube_video_rendering_with_unsupported_sources(self): - """ - Scenario: Video component is rendered in the LMS in Youtube mode - with HTML5 sources that doesn't supported by browser - Given the course has a Video component in "Youtube_HTML5_Unsupported_Video" mode - Then the video has rendered in "Youtube" mode - """ - self.metadata = self.metadata_for_mode('youtube_html5_unsupported_video') - self.navigate_to_video() - - # Verify that the video has rendered in "Youtube" mode - self.assertTrue(self.video.is_video_rendered('youtube')) - - -@attr(shard=19) -class Html5VideoTest(VideoBaseTest): - """ Test HTML5 Video Player """ - - def test_autoplay_disabled_for_video_component(self): - """ - Scenario: Autoplay is disabled by default for a Video component - Given the course has a Video component in "HTML5" mode - When I view the Video component - Then it does not have autoplay enabled - """ - self.metadata = self.metadata_for_mode('html5') - self.navigate_to_video() - - # Verify that the video has autoplay mode disabled - self.assertFalse(self.video.is_autoplay_enabled) - - def test_html5_video_rendering_with_unsupported_sources(self): - """ - Scenario: LMS displays an error message for HTML5 sources that are not supported by browser - Given the course has a Video component in "HTML5_Unsupported_Video" mode - When I view the Video component - Then and error message is shown - And the error message has the correct text - """ - self.metadata = self.metadata_for_mode('html5_unsupported_video') - self.navigate_to_video_no_render() - - # Verify that error message is shown - self.assertTrue(self.video.is_error_message_shown) - - # Verify that error message has correct text - correct_error_message_text = 'No playable video sources found.' - self.assertIn(correct_error_message_text, self.video.error_message_text) - - # Verify that spinner is not shown - self.assertFalse(self.video.is_spinner_shown) - - def test_download_button_wo_english_transcript(self): - """ - Scenario: Download button works correctly w/o english transcript in HTML5 mode - Given the course has a Video component in "HTML5" mode - And I have defined a downloadable non-english transcript for the video - And I have uploaded a non-english transcript file to assets - Then I see the correct non-english text in the captions - And the non-english transcript downloads correctly - """ - data = {'download_track': True, 'transcripts': {'zh': 'chinese_transcripts.srt'}} - self.metadata = self.metadata_for_mode('html5', additional_data=data) - self.assets.append('chinese_transcripts.srt') - - # go to video - self.navigate_to_video() - - # check if we see "好 各位同学" text in the captions - unicode_text = u"好 各位同学" - self.assertIn(unicode_text, self.video.captions_text) - - # check if we can download transcript in "srt" format that has text "好 各位同学" - unicode_text = u"好 各位同学" - self.assertTrue(self.video.downloaded_transcript_contains_text('srt', unicode_text)) - - def test_download_button_two_transcript_languages(self): - """ - Scenario: Download button works correctly for multiple transcript languages in HTML5 mode - Given the course has a Video component in "HTML5" mode - And I have defined a downloadable non-english transcript for the video - And I have defined english subtitles for the video - Then I see the correct english text in the captions - And the english transcript downloads correctly - And I see the correct non-english text in the captions - And the non-english transcript downloads correctly - """ - self.assets.extend(['chinese_transcripts.srt', 'subs_3_yD_cEKoCk.srt.sjson']) - data = {'download_track': True, 'transcripts': {'zh': 'chinese_transcripts.srt'}, 'sub': '3_yD_cEKoCk'} - self.metadata = self.metadata_for_mode('html5', additional_data=data) - - # go to video - self.navigate_to_video() - - # check if "Welcome to edX." text in the captions - self.assertIn('Welcome to edX.', self.video.captions_text) - - self.video.wait_for_element_visibility('.transcript-end', 'Transcript has loaded') - - # check if we can download transcript in "srt" format that has text "Welcome to edX." - self.assertTrue(self.video.downloaded_transcript_contains_text('srt', 'Welcome to edX.')) - - # select language with code "zh" - self.assertTrue(self.video.select_language('zh')) - - # check if we see "好 各位同学" text in the captions - unicode_text = u"好 各位同学" - - self.assertIn(unicode_text, self.video.captions_text) - - # Then I can download transcript in "srt" format that has text "好 各位同学" - unicode_text = u"好 各位同学" - self.assertTrue(self.video.downloaded_transcript_contains_text('srt', unicode_text)) - - def test_cc_button_with_english_transcript(self): - """ - Scenario: CC button works correctly with only english transcript in HTML5 mode - Given the course has a Video component in "HTML5" mode - And I have defined english subtitles for the video - And I have uploaded an english transcript file to assets - Then I see the correct text in the captions - """ - self.assets.append('subs_3_yD_cEKoCk.srt.sjson') - data = {'sub': '3_yD_cEKoCk'} - self.metadata = self.metadata_for_mode('html5', additional_data=data) - - # go to video - self.navigate_to_video() - - # make sure captions are opened - self.video.show_captions() - - # check if we see "Welcome to edX." text in the captions - self.assertIn("Welcome to edX.", self.video.captions_text) - - def test_cc_button_wo_english_transcript(self): - """ - Scenario: CC button works correctly w/o english transcript in HTML5 mode - Given the course has a Video component in "HTML5" mode - And I have defined a non-english transcript for the video - And I have uploaded a non-english transcript file to assets - Then I see the correct text in the captions - """ - self.assets.append('chinese_transcripts.srt') - data = {'transcripts': {'zh': 'chinese_transcripts.srt'}} - self.metadata = self.metadata_for_mode('html5', additional_data=data) - - # go to video - self.navigate_to_video() - - # make sure captions are opened - self.video.show_captions() - - # check if we see "好 各位同学" text in the captions - unicode_text = u"好 各位同学" - self.assertIn(unicode_text, self.video.captions_text) - - def test_video_rendering(self): - """ - Scenario: Video component is fully rendered in the LMS in HTML5 mode - Given the course has a Video component in "HTML5" mode - Then the video has rendered in "HTML5" mode - And video sources are correct - """ - self.metadata = self.metadata_for_mode('html5') - - self.navigate_to_video() - - self.assertTrue(self.video.is_video_rendered('html5')) - - self.assertTrue(all([source in HTML5_SOURCES for source in self.video.sources])) - - -@attr(shard=13) -class YouTubeQualityTest(VideoBaseTest): - """ Test YouTube Video Quality Button """ - - @skip_if_browser('firefox') - def test_quality_button_visibility(self): - """ - Scenario: Quality button appears on play. - - Given the course has a Video component in "Youtube" mode - Then I see video button "quality" is hidden - And I click video button "play" - Then I see video button "quality" is visible - """ - self.navigate_to_video() - - self.assertFalse(self.video.is_quality_button_visible) - - self.video.click_player_button('play') - - self.video.wait_for(lambda: self.video.is_quality_button_visible, 'waiting for quality button to appear') - - @skip_if_browser('firefox') - def test_quality_button_works_correctly(self): - """ - Scenario: Quality button works correctly. - - Given the course has a Video component in "Youtube" mode - And I click video button "play" - And I see video button "quality" is inactive - And I click video button "quality" - Then I see video button "quality" is active - """ - self.navigate_to_video() - - self.video.click_player_button('play') - - self.video.wait_for(lambda: self.video.is_quality_button_visible, 'waiting for quality button to appear') - - self.assertFalse(self.video.is_quality_button_active) - - self.video.click_player_button('quality') - - self.video.wait_for(lambda: self.video.is_quality_button_active, 'waiting for quality button activation') - - @attr('a11y') class LMSVideoBlockA11yTest(VideoBaseTest): """ @@ -973,166 +246,3 @@ class LMSVideoBlockA11yTest(VideoBaseTest): include=["div.video"] ) self.video.a11y_audit.check_for_accessibility_errors() - - -@attr(shard=11) -class VideoPlayOrderTest(VideoBaseTest): - """ - Test video play order with multiple videos - - Priority of video formats is: - * Youtube - * HLS - * HTML5 - """ - - def setUp(self): - super(VideoPlayOrderTest, self).setUp() - - def test_play_youtube_video(self): - """ - Scenario: Correct video is played when we have different video formats. - - Given the course has a Video component with Youtube, HTML5 and HLS sources available. - When I view the Video component - Then it should play the Youtube video - """ - additional_data = {'youtube_id_1_0': 'b7xgknqkQk8'} - self.metadata = self.metadata_for_mode('html5_and_hls', additional_data=additional_data) - self.navigate_to_video() - - # Verify that the video is youtube - self.assertTrue(self.video.is_video_rendered('youtube')) - - def test_play_html5_hls_video(self): - """ - Scenario: HLS video is played when we have HTML5 and HLS video formats only. - - Given the course has a Video component with HTML5 and HLS sources available. - When I view the Video component - Then it should play the HLS video - """ - self.metadata = self.metadata_for_mode('html5_and_hls') - self.navigate_to_video() - - # Verify that the video is hls - self.assertTrue(self.video.is_video_rendered('hls')) - - -@attr(shard=11) -class HLSVideoTest(VideoBaseTest): - """ - Tests related to HLS video - """ - - def test_video_play_pause(self): - """ - Scenario: Video play and pause is working as expected for hls video - - Given the course has a Video component with only HLS source available. - When I view the Video component - Then I can see play and pause are working as expected - """ - self.metadata = self.metadata_for_mode('hls') - self.navigate_to_video() - - self.video.click_player_button('play') - self.assertIn(self.video.state, ['buffering', 'playing']) - self.video.click_player_button('pause') - self.assertEqual(self.video.state, 'pause') - - def test_video_seek(self): - """ - Scenario: Video seek is working as expected for hls video - - Given the course has a Video component with only HLS source available. - When I view the Video component - Then I can seek the video as expected - """ - self.metadata = self.metadata_for_mode('hls') - self.navigate_to_video() - - self.video.click_player_button('play') - self.video.wait_for_position('0:02') - self.video.click_player_button('pause') - self.video.seek('0:05') - self.assertEqual(self.video.position, '0:05') - - def test_video_download_link(self): - """ - Scenario: Correct video url is selected for download - - Given the course has a Video component with Youtube, HTML5 and HLS sources available. - When I view the Video component - Then HTML5 video download url is available - """ - self.metadata = self.metadata_for_mode('html5_and_hls', additional_data={'download_video': True}) - self.navigate_to_video() - - # Verify that the video download url is correct - self.assertEqual(self.video.video_download_url, HTML5_SOURCES[0]) - - def test_no_video_download_link_for_hls(self): - """ - Scenario: Video download url is not shown for hls videos - - Given the course has a Video component with only HLS sources available. - When I view the Video component - Then there is no video download url shown - """ - additional_data = {'download_video': True} - self.metadata = self.metadata_for_mode('hls', additional_data=additional_data) - self.navigate_to_video() - - # Verify that the video download url is not shown - self.assertEqual(self.video.video_download_url, None) - - def test_hls_video_with_youtube_delayed_response_time(self): - """ - Scenario: HLS video is rendered when the YouTube API response time is slow - Given the YouTube server response time is greater than 1.5 seconds - And the course has a Video component with Youtube, HTML5 and HLS sources available - Then the HLS video is rendered - """ - # configure youtube server - self.youtube_configuration.update({ - 'time_to_response': 7.0, - }) - - self.metadata = self.metadata_for_mode('html5_and_hls', additional_data={'youtube_id_1_0': 'b7xgknqkQk8'}) - self.navigate_to_video() - self.assertTrue(self.video.is_video_rendered('hls')) - - def test_hls_video_with_transcript(self): - """ - Scenario: Transcript work as expected for an HLS video - - Given the course has a Video component with "HLS" video only - And I have defined a transcript for the video - Then I see the correct text in the captions for transcript - Then I play, pause and seek to 0:00 - Then I click on a caption line - And video position should be updated accordingly - Then I change video position - And video caption should be updated accordingly - """ - data = {'transcripts': {'zh': 'transcript.srt'}} - self.metadata = self.metadata_for_mode('hls', additional_data=data) - self.assets.append('transcript.srt') - self.navigate_to_video() - - self.assertIn("Hi, edX welcomes you0.", self.video.captions_text) - - # This is required to load the video - self.video.click_player_button('play') - # Below 2 steps are required to test the caption line click scenario - self.video.click_player_button('pause') - self.video.seek('0:00') - - for line_no in range(5): - self.video.click_transcript_line(line_no=line_no) - self.video.wait_for_position(u'0:0{}'.format(line_no)) - - for line_no in range(5): - self.video.seek(u'0:0{}'.format(line_no)) - self.assertEqual(self.video.active_caption_text, u'Hi, edX welcomes you{}.'.format(line_no)) diff --git a/common/test/acceptance/tests/video/test_video_times.py b/common/test/acceptance/tests/video/test_video_times.py deleted file mode 100644 index b641fe317d..0000000000 --- a/common/test/acceptance/tests/video/test_video_times.py +++ /dev/null @@ -1,79 +0,0 @@ -""" -Acceptance tests for Video Times(Start, End and Finish) functionality. -""" - - -from common.test.acceptance.tests.video.test_video_module import VideoBaseTest - - -class VideoTimesTest(VideoBaseTest): - """ Test Video Player Times """ - shard = 21 - - def test_video_start_time(self): - """ - Scenario: Start time works for Youtube video - Given we have a video in "Youtube" mode with start_time set to 00:00:10 - And I see video slider at "0:10" position - And I click video button "play" - Then video starts playing at or after start_time(00:00:10) - - """ - data = {'start_time': '00:00:10'} - self.metadata = self.metadata_for_mode('youtube', additional_data=data) - - # go to video - self.navigate_to_video() - - self.assertEqual(self.video.position, '0:10') - - self.video.click_player_button('play') - - self.assertGreaterEqual(int(self.video.position.split(':')[1]), 10) - - def test_video_end_time_with_default_start_time(self): - """ - Scenario: End time works for Youtube video if starts playing from beginning. - Given we have a video in "Youtube" mode with end time set to 00:00:05 - And I click video button "play" - And I wait until video stop playing - Then I see video slider at "0:05" position - - """ - data = {'end_time': '00:00:05'} - self.metadata = self.metadata_for_mode('youtube', additional_data=data) - - # go to video - self.navigate_to_video() - - self.video.click_player_button('play') - - # wait until video stop playing - self.video.wait_for_state('pause') - - self.assertIn(self.video.position, ('0:05', '0:06')) - - def test_video_start_time_and_end_time(self): - """ - Scenario: Start time and end time work together for Youtube video. - Given we a video in "Youtube" mode with start time set to 00:00:10 and end_time set to 00:00:15 - And I see video slider at "0:10" position - And I click video button "play" - Then I wait until video stop playing - Then I see video slider at "0:15" position - - """ - data = {'start_time': '00:00:10', 'end_time': '00:00:15'} - self.metadata = self.metadata_for_mode('youtube', additional_data=data) - - # go to video - self.navigate_to_video() - - self.assertEqual(self.video.position, '0:10') - - self.video.click_player_button('play') - - # wait until video stop playing - self.video.wait_for_state('pause') - - self.assertIn(self.video.position, ('0:15', '0:16'))
- zzzz