From bd4be00ae42d3888a498443ae0933378d671dac4 Mon Sep 17 00:00:00 2001 From: polesye <s2pak.anton@gmail.com> Date: Mon, 17 Mar 2014 14:20:43 +0200 Subject: [PATCH] BLD-534: Add acceptance test. --- lms/djangoapps/courseware/features/video.feature | 59 +++++++++++++++++++++++++++++++++++++++++++---------------- lms/djangoapps/courseware/features/video.py | 188 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------------------------------------------- 2 files changed, 168 insertions(+), 79 deletions(-) diff --git a/lms/djangoapps/courseware/features/video.feature b/lms/djangoapps/courseware/features/video.feature index 1051143..8556bed 100644 --- a/lms/djangoapps/courseware/features/video.feature +++ b/lms/djangoapps/courseware/features/video.feature @@ -4,8 +4,8 @@ Feature: LMS Video component # 1 Scenario: Video component stores position correctly when page is reloaded - Given the course has a Video component in Youtube mode - Then when I view the video it has rendered in Youtube mode + Given the course has a Video component in "Youtube" mode + When the video has rendered in "Youtube" mode And I click video button "play" Then I seek video to "10" seconds And I click video button "pause" @@ -15,13 +15,13 @@ Feature: LMS Video component # 2 Scenario: Video component is fully rendered in the LMS in HTML5 mode - Given the course has a Video component in HTML5 mode - Then when I view the video it has rendered in HTML5 mode + Given the course has a Video component in "HTML5" mode + When the video has rendered in "HTML5" mode And all sources are correct # 3 # Firefox doesn't have HTML5 (only mp4 - fix here) - # Disabled because does_not_autoplay fails with the + # Disabled because does_not_autoplay fails with the # selenium upgrade from 2.34.0 to 2.39.0. See TE-368 # @skip_firefox # Scenario: Autoplay is disabled in LMS for a Video component @@ -32,30 +32,30 @@ Feature: LMS Video component # Youtube testing Scenario: Video component is fully rendered in the LMS in Youtube mode with HTML5 sources Given youtube server is up and response time is 0.4 seconds - And the course has a Video component in Youtube_HTML5 mode - Then when I view the video it has rendered in Youtube mode + And the course has a Video component in "Youtube_HTML5" mode + When the video has rendered in "Youtube" mode # 5 Scenario: Video component is not rendered in the LMS in Youtube mode with HTML5 sources Given youtube server is up and response time is 2 seconds - And the course has a Video component in Youtube_HTML5 mode - Then when I view the video it has rendered in HTML5 mode + And the course has a Video component in "Youtube_HTML5" mode + When the video has rendered in "HTML5" mode # 6 Scenario: Video component is rendered in the LMS in Youtube mode without HTML5 sources Given youtube server is up and response time is 2 seconds - And the course has a Video component in Youtube mode - Then when I view the video it has rendered in Youtube mode + And the course has a Video component in "Youtube" mode + When the video has rendered in "Youtube" mode # 7 Scenario: Video component is rendered in the LMS in Youtube mode with HTML5 sources that doesn't supported by browser Given youtube server is up and response time is 2 seconds - And the course has a Video component in Youtube_HTML5_Unsupported_Video mode - Then when I view the video it has rendered in Youtube mode + And the course has a Video component in "Youtube_HTML5_Unsupported_Video" mode + When the video has rendered in "Youtube" mode # 8 Scenario: Video component is rendered in the LMS in HTML5 mode with HTML5 sources that doesn't supported by browser - Given the course has a Video component in HTML5_Unsupported_Video mode + Given the course has a Video component in "HTML5_Unsupported_Video" mode Then error message is shown And error message has correct text @@ -136,7 +136,7 @@ Feature: LMS Video component # 15 Scenario: CC button is hidden if no translations - Given the course has a Video component in Youtube mode + Given the course has a Video component in "Youtube" mode Then button "CC" is hidden # 16 @@ -152,7 +152,7 @@ Feature: LMS Video component # 17 Scenario: Video is aligned correctly if transcript is hidden in fullscreen mode - Given the course has a Video component in Youtube mode + Given the course has a Video component in "Youtube" mode And I click video button "fullscreen" Then I see video aligned correctly without enabled transcript @@ -251,3 +251,30 @@ Feature: LMS Video component | {"zh": "chinese_transcripts.srt"} | true | And I see "好 各位同学" text in the captions Then I can download transcript in "srt" format that has text "好 各位同学" + + # 25 + Scenario: Verify that each video in each sub-section includes a transcript for non-Youtube countries. + Given youtube server is up and response time is 2 seconds + And I am registered for the course "test_course" + And I have a "subs_OEoXaMPEzfM.srt.sjson" transcript file in assets + And I have a "subs_b7xgknqkQk8.srt.sjson" transcript file in assets + And I have a "chinese_transcripts.srt" transcript file in assets + And it has videos "A, B" in "Youtube_HTML5" mode in position "1" of sequential: + | sub | + | OEoXaMPEzfM | + | b7xgknqkQk8 | + And a video "C" in "Youtube_HTML5" mode in position "2" of sequential: + | transcripts | + | {"zh": "chinese_transcripts.srt"} | + And a video "D" in "Youtube_HTML5" mode in position "3" of sequential + And I open the section with videos + Then videos have rendered in "HTML5" mode + And I see "Hi, welcome to Edx." text in the captions + And I see "Equal transcripts" text in the captions + When I open video "C" + Then the video has rendered in "HTML5" mode + And I make sure captions are opened + And I see "好 各位同学" text in the captions + When I open video "D" + Then the video has rendered in "HTML5" mode + And the video does not show the captions diff --git a/lms/djangoapps/courseware/features/video.py b/lms/djangoapps/courseware/features/video.py index 414371c..0fd58a5 100644 --- a/lms/djangoapps/courseware/features/video.py +++ b/lms/djangoapps/courseware/features/video.py @@ -1,12 +1,12 @@ # -*- coding: utf-8 -*- # pylint: disable=C0111 -from lettuce import world, step -import os +from lettuce import world, step, before import json +import os import time import requests -from common import i_am_registered_for_the_course, section_location, visit_scenario_item +from common import i_am_registered_for_the_course, visit_scenario_item from django.utils.translation import ugettext as _ from django.conf import settings from cache_toolbox.core import del_cached_content @@ -45,7 +45,10 @@ VIDEO_MENUS = { } coursenum = 'test_course' -sequence = {} + +@before.each_scenario +def setUp(scenario): + world.video_sequences = {} class ReuqestHandlerWithSessionId(object): @@ -86,28 +89,26 @@ class ReuqestHandlerWithSessionId(object): return True return False -def add_video_to_course(course, player_mode, hashes, display_name='Video'): - category = 'video' - +def get_metadata(parent_location, player_mode, data, display_name='Video'): kwargs = { - 'parent_location': section_location(course), - 'category': category, + 'parent_location': parent_location, + 'category': 'video', 'display_name': display_name, 'metadata': {}, } - if hashes: - kwargs['metadata'].update(hashes[0]) + if data: + conversions = { + 'transcripts': json.loads, + 'download_track': json.loads, + 'download_video': json.loads, + } - conversions = { - 'transcripts': json.loads, - 'download_track': json.loads, - 'download_video': json.loads, - } + for key in data: + if key in conversions: + data[key] = conversions[key](data[key]) - for key in kwargs['metadata']: - if key in conversions: - kwargs['metadata'][key] = conversions[key](kwargs['metadata'][key]) + kwargs['metadata'].update(data) if player_mode == 'html5': kwargs['metadata'].update({ @@ -134,19 +135,53 @@ def add_video_to_course(course, player_mode, hashes, display_name='Video'): 'html5_sources': HTML5_SOURCES_INCORRECT }) + return kwargs + + +def add_videos_to_course(course, player_mode=None, display_names=None, hashes=None): + parent_location = add_vertical_to_course(course) + kwargs = { + 'course': course, + 'parent_location': parent_location, + 'player_mode': player_mode, + 'display_name': display_names[0], + } + + if hashes: + for index, item_data in enumerate(hashes): + kwargs.update({ + 'display_name': display_names[index], + 'data': item_data, + }) + add_video_to_course(**kwargs) + else: + add_video_to_course(**kwargs) + + +def add_video_to_course(course, parent_location=None, player_mode=None, data=None, display_name='Video'): + if not parent_location: + parent_location = add_vertical_to_course(course) + kwargs = get_metadata(parent_location, player_mode, data, display_name=display_name) world.scenario_dict['VIDEO'] = world.ItemFactory.create(**kwargs) world.wait_for_present('.is-initialized') world.wait_for_invisible('.video-wrapper .spinner') -def _get_sjson_filename(videoId, lang): - if lang == 'en': - return 'subs_{0}.srt.sjson'.format(videoId) - else: - return '{0}_subs_{1}.srt.sjson'.format(lang, videoId) +def add_vertical_to_course(course_num): + world.scenario_dict['LAST_VERTICAL'] = world.ItemFactory.create( + parent_location=world.scenario_dict['SECTION'].location, + category='vertical', + display_name='Test Vertical-{}'.format(len(set(world.video_sequences.values()))), + ) + + return last_vertical_location(course_num) + +def last_vertical_location(course_num): + return world.scenario_dict['LAST_VERTICAL'].location._replace(course=course_num) -def _upload_file(filename, location): + +def upload_file(filename, location): path = os.path.join(TEST_ROOT, 'uploads/', filename) f = open(os.path.abspath(path)) mime_type = "application/json" @@ -159,27 +194,28 @@ def _upload_file(filename, location): del_cached_content(content.location) -def _navigate_to_an_item_in_a_sequence(number): +def navigate_to_an_item_in_a_sequence(number): sequence_css = '#sequence-list a[data-element="{0}"]'.format(number) world.css_click(sequence_css) -def _change_video_speed(speed): +def change_video_speed(speed): world.browser.execute_script("$('.speeds').addClass('open')") speed_css = 'li[data-speed="{0}"] a'.format(speed) world.css_click(speed_css) -def _open_menu(menu): + +def open_menu(menu): world.browser.execute_script("$('{selector}').parent().addClass('open')".format( selector=VIDEO_MENUS[menu] )) -def _get_all_dimensions(): - video = _get_dimensions('.video-player iframe, .video-player video') - wrapper = _get_dimensions('.tc-wrapper') - controls = _get_dimensions('.video-controls') - progress_slider = _get_dimensions('.video-controls > .slider') +def get_all_dimensions(): + video = get_dimensions('.video-player iframe, .video-player video') + wrapper = get_dimensions('.tc-wrapper') + controls = get_dimensions('.video-controls') + progress_slider = get_dimensions('.video-controls > .slider') expected = dict(wrapper) expected['height'] -= controls['height'] + 0.5 * progress_slider['height'] @@ -187,30 +223,30 @@ def _get_all_dimensions(): return (video, expected) -def _get_dimensions(selector): +def get_dimensions(selector): element = world.css_find(selector).first return element._element.size -def _get_window_dimensions(): +def get_window_dimensions(): return world.browser.driver.get_window_size() -def _set_window_dimensions(width, height): +def set_window_dimensions(width, height): world.browser.driver.set_window_size(width, height) # Wait 200 ms when JS finish resizing world.wait(0.2) -def _duration(): +def duration(): """ Total duration of the video, in seconds. """ - elapsed_time, duration = _video_time() + elapsed_time, duration = video_time() return duration -def _video_time(): +def video_time(): """ Return a tuple `(elapsed_time, duration)`, each in seconds. """ @@ -221,10 +257,10 @@ def _video_time(): elapsed_str, duration_str = full_time.split(' / ') # Convert each string to seconds - return (_parse_time_str(elapsed_str), _parse_time_str(duration_str)) + return (parse_time_str(elapsed_str), parse_time_str(duration_str)) -def _parse_time_str(time_str): +def parse_time_str(time_str): """ Parse a string of the form 1:23 into seconds (int). """ @@ -237,23 +273,26 @@ def does_not_autoplay(_step, video_type): assert(world.css_find('.%s' % video_type)[0]['data-autoplay'] == 'False') -@step('the course has a Video component in (.*) mode(?:\:)?$') +@step('the course has a Video component in "([^"]*)" mode(?:\:)?$') def view_video(_step, player_mode): i_am_registered_for_the_course(_step, coursenum) - add_video_to_course(coursenum, player_mode.lower(), _step.hashes) + data = _step.hashes[0] if _step.hashes else None + add_video_to_course(coursenum, player_mode=player_mode.lower(), data=data) visit_scenario_item('SECTION') @step('a video in "([^"]*)" mode(?:\:)?$') def add_video(_step, player_mode): - add_video_to_course(coursenum, player_mode.lower(), _step.hashes) + data = _step.hashes[0] if _step.hashes else None + add_video_to_course(coursenum, player_mode=player_mode.lower(), data=data) visit_scenario_item('SECTION') -@step('a video "([^"]*)" in "([^"]*)" mode in position "([^"]*)" of sequential(?:\:)?$') -def add_video_in_position(_step, player_id, player_mode, position): - sequence[player_id] = position - add_video_to_course(coursenum, player_mode.lower(), _step.hashes, display_name=player_id) +@step('video(?:s)? "([^"]*)" in "([^"]*)" mode in position "([^"]*)" of sequential(?:\:)?$') +def add_video_in_position(_step, video_ids, player_mode, position): + sequences = {video_id.strip(): position for video_id in video_ids.split(',')} + add_videos_to_course(coursenum, player_mode=player_mode.lower(), display_names=sequences.keys(), hashes=_step.hashes) + world.video_sequences.update(sequences) @step('I open the section with videos$') @@ -262,19 +301,19 @@ def visit_video_section(_step): @step('I select the "([^"]*)" speed$') -def change_video_speed(_step, speed): - _change_video_speed(speed) +def i_select_video_speed(_step, speed): + change_video_speed(speed) @step('I select the "([^"]*)" speed on video "([^"]*)"$') def change_video_speed_on_video(_step, speed, player_id): - _navigate_to_an_item_in_a_sequence(sequence[player_id]) - _change_video_speed(speed) + navigate_to_an_item_in_a_sequence(world.video_sequences[player_id]) + change_video_speed(speed) @step('I open video "([^"]*)"$') def open_video(_step, player_id): - _navigate_to_an_item_in_a_sequence(sequence[player_id]) + navigate_to_an_item_in_a_sequence(world.video_sequences[player_id]) @step('video "([^"]*)" should start playing at speed "([^"]*)"$') @@ -288,7 +327,7 @@ def set_youtube_response_timeout(_step, time): world.youtube.config['time_to_response'] = float(time) -@step('when I view the video it has rendered in (.*) mode$') +@step('the video has rendered in "([^"]*)" mode$') def video_is_rendered(_step, mode): modes = { 'html5': 'video', @@ -299,6 +338,20 @@ def video_is_rendered(_step, mode): assert world.is_css_present('.speed_link') +@step('videos have rendered in "([^"]*)" mode$') +def videos_are_rendered(_step, mode): + modes = { + 'html5': 'video', + 'youtube': 'iframe' + } + html_tag = modes[mode.lower()] + + actual = len(world.css_find('.video {0}'.format(html_tag))) + expected = len(world.css_find('.xmodule_VideoModule')) + assert actual == expected + assert world.is_css_present('.speed_link') + + @step('all sources are correct$') def all_sources_are_correct(_step): elements = world.css_find('.video-player video source') @@ -333,7 +386,7 @@ def set_captions_visibility_state(_step, captions_state): @step('I see video menu "([^"]*)" with correct items$') def i_see_menu(_step, menu): - _open_menu(menu) + open_menu(menu) menu_items = world.css_find(VIDEO_MENUS[menu] + ' li') video = world.scenario_dict['VIDEO'] transcripts = dict(video.transcripts) @@ -388,7 +441,7 @@ def start_playing_video_from_n_seconds(_step, position): @step('I see duration "([^"]*)"$') def i_see_duration(_step, position): world.wait_for( - func=lambda _: _duration() == _parse_time_str(position), + func=lambda _: duration() == parse_time_str(position), timeout=5 ) @@ -402,7 +455,7 @@ def seek_video_to_n_seconds(_step, seconds): @step('I have a "([^"]*)" transcript file in assets$') def upload_to_assets(_step, filename): - _upload_file(filename, world.scenario_dict['COURSE'].location) + upload_file(filename, world.scenario_dict['COURSE'].location) @step('button "([^"]*)" is hidden$') @@ -419,20 +472,20 @@ def is_hidden_menu(_step, menu): def video_alignment(_step, transcript_visibility): # Width of the video container in css equal 75% of window if transcript enabled wrapper_width = 75 if transcript_visibility == "with" else 100 - initial = _get_window_dimensions() + initial = get_window_dimensions() - _set_window_dimensions(300, 600) - real, expected = _get_all_dimensions() + set_window_dimensions(300, 600) + real, expected = get_all_dimensions() width = round(100 * real['width']/expected['width']) == wrapper_width - _set_window_dimensions(600, 300) - real, expected = _get_all_dimensions() + set_window_dimensions(600, 300) + real, expected = get_all_dimensions() height = abs(expected['height'] - real['height']) <= 5 # Restore initial window size - _set_window_dimensions( + set_window_dimensions( initial['width'], initial['height'] ) @@ -473,3 +526,12 @@ def select_transcript_format(_step, format): assert world.css_find(menu_selector + ' .active a')[0]['data-value'] == format assert world.css_has_text(button_selector, '.' + format, strip=True) + + +@step('video (.*) show the captions$') +def shows_captions(_step, show_captions): + if 'not' in show_captions or 'n\'t' in show_captions: + assert world.is_css_present('div.video.closed') + else: + assert world.is_css_not_present('div.video.closed') + -- libgit2 0.26.0