Merge pull request #2953 from edx/anton/check-transcripts-in-china
Video: Add acceptance test to verify transcripts for non-Youtube countries.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 _upload_file(filename, location):
|
||||
def last_vertical_location(course_num):
|
||||
return world.scenario_dict['LAST_VERTICAL'].location._replace(course=course_num)
|
||||
|
||||
|
||||
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')
|
||||
|
||||
|
||||
Reference in New Issue
Block a user