Merge pull request #4307 from edx/ammar/bok-choy-video-handout-tests
CMS Video Handout Tests
This commit is contained in:
@@ -1,71 +0,0 @@
|
||||
@shard_3 @requires_stub_youtube
|
||||
Feature: CMS Video Component Handout
|
||||
As a course author, I want to be able to create video handout
|
||||
|
||||
# 1
|
||||
Scenario: Handout uploading works correctly
|
||||
Given I have created a Video component with handout file "textbook.pdf"
|
||||
And I save changes
|
||||
Then I can see video button "handout"
|
||||
And I can download handout file with mime type "application/pdf"
|
||||
|
||||
# 2
|
||||
Scenario: Handout downloading works correctly w/ preliminary saving
|
||||
Given I have created a Video component with handout file "textbook.pdf"
|
||||
And I save changes
|
||||
And I edit the component
|
||||
And I open tab "Advanced"
|
||||
And I can download handout file in editor with mime type "application/pdf"
|
||||
|
||||
# 3
|
||||
Scenario: Handout downloading works correctly w/o preliminary saving
|
||||
Given I have created a Video component with handout file "textbook.pdf"
|
||||
And I can download handout file in editor with mime type "application/pdf"
|
||||
|
||||
# 4
|
||||
Scenario: Handout clearing works correctly w/ preliminary saving
|
||||
Given I have created a Video component with handout file "textbook.pdf"
|
||||
And I save changes
|
||||
And I can download handout file with mime type "application/pdf"
|
||||
And I edit the component
|
||||
And I open tab "Advanced"
|
||||
And I clear handout
|
||||
And I save changes
|
||||
Then I do not see video button "handout"
|
||||
|
||||
# 5
|
||||
Scenario: Handout clearing works correctly w/o preliminary saving
|
||||
Given I have created a Video component with handout file "asset.html"
|
||||
And I clear handout
|
||||
And I save changes
|
||||
Then I do not see video button "handout"
|
||||
|
||||
# 6
|
||||
Scenario: User can easy replace the handout by another one w/ preliminary saving
|
||||
Given I have created a Video component with handout file "asset.html"
|
||||
And I save changes
|
||||
Then I can see video button "handout"
|
||||
And I can download handout file with mime type "text/html"
|
||||
And I edit the component
|
||||
And I open tab "Advanced"
|
||||
And I replace handout file by "textbook.pdf"
|
||||
And I save changes
|
||||
Then I can see video button "handout"
|
||||
And I can download handout file with mime type "application/pdf"
|
||||
|
||||
# 7
|
||||
Scenario: User can easy replace the handout by another one w/o preliminary saving
|
||||
Given I have created a Video component with handout file "asset.html"
|
||||
And I replace handout file by "textbook.pdf"
|
||||
And I save changes
|
||||
Then I can see video button "handout"
|
||||
And I can download handout file with mime type "application/pdf"
|
||||
|
||||
# 8
|
||||
Scenario: Upload file "A" -> Remove it -> Upload file "B"
|
||||
Given I have created a Video component with handout file "asset.html"
|
||||
And I clear handout
|
||||
And I upload handout file "textbook.pdf"
|
||||
And I save changes
|
||||
Then I can see video button "handout"
|
||||
And I can download handout file with mime type "application/pdf"
|
||||
@@ -2,6 +2,7 @@
|
||||
Fixture to create a course and course components (XBlocks).
|
||||
"""
|
||||
|
||||
import mimetypes
|
||||
import json
|
||||
import re
|
||||
import datetime
|
||||
@@ -407,10 +408,10 @@ class CourseFixture(StudioApiFixture):
|
||||
test_dir = path(__file__).abspath().dirname().dirname().dirname()
|
||||
|
||||
for asset_name in self._assets:
|
||||
srt_path = test_dir + '/data/uploads/' + asset_name
|
||||
asset_file_path = test_dir + '/data/uploads/' + asset_name
|
||||
|
||||
asset_file = open(srt_path)
|
||||
files = {'file': (asset_name, asset_file)}
|
||||
asset_file = open(asset_file_path)
|
||||
files = {'file': (asset_name, asset_file, mimetypes.guess_type(asset_file_path)[0])}
|
||||
|
||||
headers = {
|
||||
'Accept': 'application/json',
|
||||
|
||||
@@ -62,6 +62,22 @@ class UnitPage(PageObject):
|
||||
'Wait for draft mode to be activated'
|
||||
).fulfill()
|
||||
|
||||
def set_unit_visibility(self, visibility):
|
||||
"""
|
||||
Set unit visibility state
|
||||
|
||||
Arguments:
|
||||
visibility (str): private or public
|
||||
|
||||
"""
|
||||
self.q(css='select[name="visibility-select"] option[value="{}"]'.format(visibility)).first.click()
|
||||
self.wait_for_ajax()
|
||||
|
||||
|
||||
COMPONENT_BUTTONS = {
|
||||
'advanced_tab': '.editor-tabs li.inner_tab_wrap:nth-child(2) > a',
|
||||
'save_settings': '.action-save',
|
||||
}
|
||||
|
||||
class Component(PageObject):
|
||||
"""
|
||||
@@ -125,3 +141,26 @@ class Component(PageObject):
|
||||
an initialized :class:`.ContainerPage` for that xblock.
|
||||
"""
|
||||
return ContainerPage(self.browser, self.locator).visit()
|
||||
|
||||
def _click_button(self, button_name):
|
||||
"""
|
||||
Click on a button as specified by `button_name`
|
||||
|
||||
Arguments:
|
||||
button_name (str): button name
|
||||
|
||||
"""
|
||||
self.q(css=COMPONENT_BUTTONS[button_name]).first.click()
|
||||
self.wait_for_ajax()
|
||||
|
||||
def open_advanced_tab(self):
|
||||
"""
|
||||
Click on Advanced Tab.
|
||||
"""
|
||||
self._click_button('advanced_tab')
|
||||
|
||||
def save_settings(self):
|
||||
"""
|
||||
Click on settings Save button.
|
||||
"""
|
||||
self._click_button('save_settings')
|
||||
|
||||
142
common/test/acceptance/pages/studio/video/video.py
Normal file
142
common/test/acceptance/pages/studio/video/video.py
Normal file
@@ -0,0 +1,142 @@
|
||||
"""
|
||||
CMS Video
|
||||
"""
|
||||
|
||||
import os
|
||||
import requests
|
||||
from bok_choy.page_object import PageObject
|
||||
from bok_choy.promise import EmptyPromise, Promise
|
||||
from bok_choy.javascript import wait_for_js, js_defined
|
||||
|
||||
CLASS_SELECTORS = {
|
||||
'video_init': '.is-initialized',
|
||||
'video_xmodule': '.xmodule_VideoModule',
|
||||
'video_spinner': '.video-wrapper .spinner',
|
||||
'video_controls': 'section.video-controls',
|
||||
'attach_handout': '.upload-dialog > input[type="file"]',
|
||||
'upload_dialog': '.wrapper-modal-window-assetupload',
|
||||
}
|
||||
|
||||
BUTTON_SELECTORS = {
|
||||
'handout_download': '.video-handout.video-download-button a',
|
||||
'handout_download_editor': '.wrapper-comp-setting.file-uploader .download-action',
|
||||
'upload_handout': '.upload-action',
|
||||
'handout_submit': '.action-upload',
|
||||
'handout_clear': '.wrapper-comp-setting.file-uploader .setting-clear',
|
||||
}
|
||||
|
||||
|
||||
@js_defined('window.Video', 'window.RequireJS.require', 'window.jQuery', 'window.XModule', 'window.XBlock',
|
||||
'window.MathJax.isReady')
|
||||
class VidoComponentPage(PageObject):
|
||||
"""
|
||||
CMS Video Component Page
|
||||
"""
|
||||
|
||||
url = None
|
||||
|
||||
@wait_for_js
|
||||
def is_browser_on_page(self):
|
||||
return self.q(css='div{0}'.format(CLASS_SELECTORS['video_xmodule'])).present
|
||||
|
||||
def _wait_for(self, check_func, desc, result=False, timeout=200):
|
||||
"""
|
||||
Calls the method provided as an argument until the Promise satisfied or BrokenPromise
|
||||
|
||||
Arguments:
|
||||
check_func (callable): Promise function to be fulfilled.
|
||||
desc (str): Description of the Promise, used in log messages.
|
||||
result (bool): Indicates whether we need result from Promise or not
|
||||
timeout (float): Maximum number of seconds to wait for the Promise to be satisfied before timing out.
|
||||
|
||||
"""
|
||||
if result:
|
||||
return Promise(check_func, desc, timeout=timeout).fulfill()
|
||||
else:
|
||||
return EmptyPromise(check_func, desc, timeout=timeout).fulfill()
|
||||
|
||||
def wait_for_video_component_render(self):
|
||||
"""
|
||||
Wait until video component rendered completely
|
||||
"""
|
||||
self._wait_for(lambda: self.q(css=CLASS_SELECTORS['video_init']).present, 'Video Player Initialized')
|
||||
self._wait_for(lambda: not self.q(css=CLASS_SELECTORS['video_spinner']).visible, 'Video Buffering Completed')
|
||||
self._wait_for(lambda: self.q(css=CLASS_SELECTORS['video_controls']).visible, 'Player Controls are Visible')
|
||||
|
||||
def click_button(self, button_name):
|
||||
"""
|
||||
Click on a button as specified by `button_name`
|
||||
|
||||
Arguments:
|
||||
button_name (str): button name
|
||||
|
||||
"""
|
||||
self.q(css=BUTTON_SELECTORS[button_name]).first.click()
|
||||
self.wait_for_ajax()
|
||||
|
||||
def upload_handout(self, handout_filename):
|
||||
"""
|
||||
Upload a handout file to assets
|
||||
|
||||
Arguments:
|
||||
handout_filename (str): handout file name
|
||||
|
||||
"""
|
||||
handout_path = os.sep.join(__file__.split(os.sep)[:-5]) + '/data/uploads/' + handout_filename
|
||||
|
||||
self.click_button('upload_handout')
|
||||
|
||||
self.q(css=CLASS_SELECTORS['attach_handout']).results[0].send_keys(handout_path)
|
||||
|
||||
self.click_button('handout_submit')
|
||||
|
||||
# confirm upload completion
|
||||
self._wait_for(lambda: not self.q(css=CLASS_SELECTORS['upload_dialog']).present, 'Upload Handout Completed')
|
||||
|
||||
def clear_handout(self):
|
||||
"""
|
||||
Clear handout from settings
|
||||
"""
|
||||
self.click_button('handout_clear')
|
||||
|
||||
def _get_handout(self, url):
|
||||
"""
|
||||
Download handout at `url`
|
||||
"""
|
||||
kwargs = dict()
|
||||
|
||||
session_id = [{i['name']: i['value']} for i in self.browser.get_cookies() if i['name'] == u'sessionid']
|
||||
if session_id:
|
||||
kwargs.update({
|
||||
'cookies': session_id[0]
|
||||
})
|
||||
|
||||
response = requests.get(url, **kwargs)
|
||||
return response.status_code < 400, response.headers
|
||||
|
||||
def download_handout(self, mime_type, is_editor=False):
|
||||
"""
|
||||
Download handout with mime type specified by `mime_type`
|
||||
|
||||
Arguments:
|
||||
mime_type (str): mime type of handout file
|
||||
|
||||
Returns:
|
||||
tuple: Handout download result.
|
||||
|
||||
"""
|
||||
selector = BUTTON_SELECTORS['handout_download_editor'] if is_editor else BUTTON_SELECTORS['handout_download']
|
||||
|
||||
handout_url = self.q(css=selector).attrs('href')[0]
|
||||
result, headers = self._get_handout(handout_url)
|
||||
|
||||
return result, headers['content-type'] == mime_type
|
||||
|
||||
@property
|
||||
def is_handout_button_visible(self):
|
||||
"""
|
||||
Check if handout download button is visible
|
||||
"""
|
||||
# TODO! Remove .present below after bok-choy is updated to latest commit, Only .visible is enough
|
||||
return self.q(css=BUTTON_SELECTORS['handout_download']).present and self.q(
|
||||
css=BUTTON_SELECTORS['handout_download']).visible
|
||||
117
common/test/acceptance/tests/video/test_studio_video_module.py
Normal file
117
common/test/acceptance/tests/video/test_studio_video_module.py
Normal file
@@ -0,0 +1,117 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Acceptance tests for CMS Video Module.
|
||||
"""
|
||||
|
||||
from unittest import skipIf
|
||||
from ...pages.studio.auto_auth import AutoAuthPage
|
||||
from ...pages.studio.overview import CourseOutlinePage
|
||||
from ...pages.studio.video.video import VidoComponentPage
|
||||
from ...fixtures.course import CourseFixture, XBlockFixtureDesc
|
||||
from ..helpers import UniqueCourseTest, 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 = VidoComponentPage(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']
|
||||
)
|
||||
|
||||
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
|
||||
"""
|
||||
|
||||
# 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'),
|
||||
)
|
||||
)
|
||||
)
|
||||
).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').toggle_expand().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):
|
||||
"""
|
||||
Make component editable and open components Edit Dialog.
|
||||
|
||||
Arguments:
|
||||
handout_filename (str): handout file name to be uploaded
|
||||
save_settings (bool): save settings or not
|
||||
|
||||
"""
|
||||
self.unit_page.set_unit_visibility('private')
|
||||
self.unit_page.components[0].edit()
|
||||
|
||||
def open_advanced_tab(self):
|
||||
"""
|
||||
Open components advanced tab.
|
||||
"""
|
||||
self.unit_page.components[0].open_advanced_tab()
|
||||
|
||||
def save_unit_settings(self):
|
||||
"""
|
||||
Save component settings.
|
||||
"""
|
||||
self.unit_page.components[0].save_settings()
|
||||
|
||||
191
common/test/acceptance/tests/video/test_video_handout.py
Normal file
191
common/test/acceptance/tests/video/test_video_handout.py
Normal file
@@ -0,0 +1,191 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Acceptance tests for CMS Video Handout.
|
||||
"""
|
||||
|
||||
from .test_studio_video_module import CMSVideoBaseTest
|
||||
|
||||
|
||||
class VideoHandoutTest(CMSVideoBaseTest):
|
||||
"""
|
||||
CMS Video Handout Test Class
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(VideoHandoutTest, self).setUp()
|
||||
|
||||
def _create_course_unit_with_handout(self, handout_filename, save_settings=True):
|
||||
"""
|
||||
Create a course with unit and also upload handout
|
||||
|
||||
Arguments:
|
||||
handout_filename (str): handout file name to be uploaded
|
||||
save_settings (bool): save settings or not
|
||||
|
||||
"""
|
||||
self.navigate_to_course_unit()
|
||||
|
||||
self.edit_component()
|
||||
|
||||
self.open_advanced_tab()
|
||||
|
||||
self.video.upload_handout(handout_filename)
|
||||
|
||||
if save_settings:
|
||||
self.save_unit_settings()
|
||||
|
||||
def test_handout_uploads_correctly(self):
|
||||
"""
|
||||
Scenario: Handout uploading works correctly
|
||||
Given I have created a Video component with handout file "textbook.pdf"
|
||||
Then I can see video button "handout"
|
||||
And I can download handout file with mime type "application/pdf"
|
||||
"""
|
||||
self._create_course_unit_with_handout('textbook.pdf')
|
||||
|
||||
self.assertTrue(self.video.is_handout_button_visible)
|
||||
|
||||
self.assertEqual(self.video.download_handout('application/pdf'), (True, True))
|
||||
|
||||
def test_handout_download_works_with_save(self):
|
||||
"""
|
||||
Scenario: Handout downloading works correctly w/ preliminary saving
|
||||
Given I have created a Video component with handout file "textbook.pdf"
|
||||
And I save changes
|
||||
And I edit the component
|
||||
And I open tab "Advanced"
|
||||
And I can download handout file in editor with mime type "application/pdf"
|
||||
"""
|
||||
self._create_course_unit_with_handout('textbook.pdf')
|
||||
|
||||
self.edit_component()
|
||||
|
||||
self.open_advanced_tab()
|
||||
|
||||
self.assertEqual(self.video.download_handout('application/pdf', is_editor=True), (True, True))
|
||||
|
||||
def test_handout_download_works_wo_save(self):
|
||||
"""
|
||||
Scenario: Handout downloading works correctly w/o preliminary saving
|
||||
Given I have created a Video component with handout file "textbook.pdf"
|
||||
And I can download handout file in editor with mime type "application/pdf"
|
||||
"""
|
||||
self._create_course_unit_with_handout('textbook.pdf', save_settings=False)
|
||||
|
||||
self.assertEqual(self.video.download_handout('application/pdf', is_editor=True), (True, True))
|
||||
|
||||
def test_handout_clearing_works_w_save(self):
|
||||
"""
|
||||
Scenario: Handout clearing works correctly w/ preliminary saving
|
||||
Given I have created a Video component with handout file "textbook.pdf"
|
||||
And I save changes
|
||||
And I can download handout file with mime type "application/pdf"
|
||||
And I edit the component
|
||||
And I open tab "Advanced"
|
||||
And I clear handout
|
||||
And I save changes
|
||||
Then I do not see video button "handout"
|
||||
"""
|
||||
self._create_course_unit_with_handout('textbook.pdf')
|
||||
|
||||
self.assertEqual(self.video.download_handout('application/pdf'), (True, True))
|
||||
|
||||
self.edit_component()
|
||||
|
||||
self.open_advanced_tab()
|
||||
|
||||
self.video.clear_handout()
|
||||
|
||||
self.save_unit_settings()
|
||||
|
||||
self.assertFalse(self.video.is_handout_button_visible)
|
||||
|
||||
def test_handout_clearing_works_wo_save(self):
|
||||
"""
|
||||
Scenario: Handout clearing works correctly w/o preliminary saving
|
||||
Given I have created a Video component with handout file "asset.html"
|
||||
And I clear handout
|
||||
And I save changes
|
||||
Then I do not see video button "handout"
|
||||
"""
|
||||
self._create_course_unit_with_handout('asset.html', save_settings=False)
|
||||
|
||||
self.video.clear_handout()
|
||||
|
||||
self.save_unit_settings()
|
||||
|
||||
self.assertFalse(self.video.is_handout_button_visible)
|
||||
|
||||
def test_handout_replace_w_save(self):
|
||||
"""
|
||||
Scenario: User can easy replace the handout by another one w/ preliminary saving
|
||||
Given I have created a Video component with handout file "asset.html"
|
||||
And I save changes
|
||||
Then I can see video button "handout"
|
||||
And I can download handout file with mime type "text/html"
|
||||
And I edit the component
|
||||
And I open tab "Advanced"
|
||||
And I replace handout file by "textbook.pdf"
|
||||
And I save changes
|
||||
Then I can see video button "handout"
|
||||
And I can download handout file with mime type "application/pdf"
|
||||
"""
|
||||
self._create_course_unit_with_handout('asset.html')
|
||||
|
||||
self.assertTrue(self.video.is_handout_button_visible)
|
||||
|
||||
self.assertEqual(self.video.download_handout('text/html'), (True, True))
|
||||
|
||||
self.edit_component()
|
||||
|
||||
self.open_advanced_tab()
|
||||
|
||||
self.video.upload_handout('textbook.pdf')
|
||||
|
||||
self.save_unit_settings()
|
||||
|
||||
self.assertTrue(self.video.is_handout_button_visible)
|
||||
|
||||
self.assertEqual(self.video.download_handout('application/pdf'), (True, True))
|
||||
|
||||
def test_handout_replace_wo_save(self):
|
||||
"""
|
||||
Scenario: User can easy replace the handout by another one w/o preliminary saving
|
||||
Given I have created a Video component with handout file "asset.html"
|
||||
And I replace handout file by "textbook.pdf"
|
||||
And I save changes
|
||||
Then I can see video button "handout"
|
||||
And I can download handout file with mime type "application/pdf"
|
||||
"""
|
||||
self._create_course_unit_with_handout('asset.html', save_settings=False)
|
||||
|
||||
self.video.upload_handout('textbook.pdf')
|
||||
|
||||
self.save_unit_settings()
|
||||
|
||||
self.assertTrue(self.video.is_handout_button_visible)
|
||||
|
||||
self.assertEqual(self.video.download_handout('application/pdf'), (True, True))
|
||||
|
||||
def test_handout_upload_and_clear_works(self):
|
||||
"""
|
||||
Scenario: Upload file "A" -> Remove it -> Upload file "B"
|
||||
Given I have created a Video component with handout file "asset.html"
|
||||
And I clear handout
|
||||
And I upload handout file "textbook.pdf"
|
||||
And I save changes
|
||||
Then I can see video button "handout"
|
||||
And I can download handout file with mime type "application/pdf"
|
||||
"""
|
||||
self._create_course_unit_with_handout('asset.html', save_settings=False)
|
||||
|
||||
self.video.clear_handout()
|
||||
|
||||
self.video.upload_handout('textbook.pdf')
|
||||
|
||||
self.save_unit_settings()
|
||||
|
||||
self.assertTrue(self.video.is_handout_button_visible)
|
||||
|
||||
self.assertEqual(self.video.download_handout('application/pdf'), (True, True))
|
||||
1
common/test/data/uploads/.gitignore
vendored
1
common/test/data/uploads/.gitignore
vendored
@@ -1,3 +1,2 @@
|
||||
test
|
||||
test2
|
||||
asset.html
|
||||
|
||||
10
common/test/data/uploads/asset.html
Normal file
10
common/test/data/uploads/asset.html
Normal file
@@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>ASSET</title>
|
||||
</head>
|
||||
<body>
|
||||
<div>i am a dummy asset file</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user