Commit bd4be00a by polesye

BLD-534: Add acceptance test.

parent 982f24ae
...@@ -4,8 +4,8 @@ Feature: LMS Video component ...@@ -4,8 +4,8 @@ Feature: LMS Video component
# 1 # 1
Scenario: Video component stores position correctly when page is reloaded Scenario: Video component stores position correctly when page is reloaded
Given the course has a Video component in Youtube mode Given the course has a Video component in "Youtube" mode
Then when I view the video it has rendered in Youtube mode When the video has rendered in "Youtube" mode
And I click video button "play" And I click video button "play"
Then I seek video to "10" seconds Then I seek video to "10" seconds
And I click video button "pause" And I click video button "pause"
...@@ -15,13 +15,13 @@ Feature: LMS Video component ...@@ -15,13 +15,13 @@ Feature: LMS Video component
# 2 # 2
Scenario: Video component is fully rendered in the LMS in HTML5 mode Scenario: Video component is fully rendered in the LMS in HTML5 mode
Given the course has a Video component 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 When the video has rendered in "HTML5" mode
And all sources are correct And all sources are correct
# 3 # 3
# Firefox doesn't have HTML5 (only mp4 - fix here) # 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 # selenium upgrade from 2.34.0 to 2.39.0. See TE-368
# @skip_firefox # @skip_firefox
# Scenario: Autoplay is disabled in LMS for a Video component # Scenario: Autoplay is disabled in LMS for a Video component
...@@ -32,30 +32,30 @@ Feature: LMS Video component ...@@ -32,30 +32,30 @@ Feature: LMS Video component
# Youtube testing # Youtube testing
Scenario: Video component is fully rendered in the LMS in Youtube mode with HTML5 sources 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 Given youtube server is up and response time is 0.4 seconds
And the course has a Video component in Youtube_HTML5 mode And the course has a Video component in "Youtube_HTML5" mode
Then when I view the video it has rendered in Youtube mode When the video has rendered in "Youtube" mode
# 5 # 5
Scenario: Video component is not rendered in the LMS in Youtube mode with HTML5 sources 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 Given youtube server is up and response time is 2 seconds
And the course has a Video component in Youtube_HTML5 mode And the course has a Video component in "Youtube_HTML5" mode
Then when I view the video it has rendered in HTML5 mode When the video has rendered in "HTML5" mode
# 6 # 6
Scenario: Video component is rendered in the LMS in Youtube mode without HTML5 sources 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 Given youtube server is up and response time is 2 seconds
And the course has a Video component in Youtube mode And the course has a Video component in "Youtube" mode
Then when I view the video it has rendered in Youtube mode When the video has rendered in "Youtube" mode
# 7 # 7
Scenario: Video component is rendered in the LMS in Youtube mode with HTML5 sources that doesn't supported by browser 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 Given youtube server is up and response time is 2 seconds
And the course has a Video component in Youtube_HTML5_Unsupported_Video mode 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 When the video has rendered in "Youtube" mode
# 8 # 8
Scenario: Video component is rendered in the LMS in HTML5 mode with HTML5 sources that doesn't supported by browser 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 Then error message is shown
And error message has correct text And error message has correct text
...@@ -136,7 +136,7 @@ Feature: LMS Video component ...@@ -136,7 +136,7 @@ Feature: LMS Video component
# 15 # 15
Scenario: CC button is hidden if no translations 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 Then button "CC" is hidden
# 16 # 16
...@@ -152,7 +152,7 @@ Feature: LMS Video component ...@@ -152,7 +152,7 @@ Feature: LMS Video component
# 17 # 17
Scenario: Video is aligned correctly if transcript is hidden in fullscreen mode 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" And I click video button "fullscreen"
Then I see video aligned correctly without enabled transcript Then I see video aligned correctly without enabled transcript
...@@ -251,3 +251,30 @@ Feature: LMS Video component ...@@ -251,3 +251,30 @@ Feature: LMS Video component
| {"zh": "chinese_transcripts.srt"} | true | | {"zh": "chinese_transcripts.srt"} | true |
And I see "好 各位同学" text in the captions And I see "好 各位同学" text in the captions
Then I can download transcript in "srt" format that has text "好 各位同学" 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
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# pylint: disable=C0111 # pylint: disable=C0111
from lettuce import world, step from lettuce import world, step, before
import os
import json import json
import os
import time import time
import requests 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.utils.translation import ugettext as _
from django.conf import settings from django.conf import settings
from cache_toolbox.core import del_cached_content from cache_toolbox.core import del_cached_content
...@@ -45,7 +45,10 @@ VIDEO_MENUS = { ...@@ -45,7 +45,10 @@ VIDEO_MENUS = {
} }
coursenum = 'test_course' coursenum = 'test_course'
sequence = {}
@before.each_scenario
def setUp(scenario):
world.video_sequences = {}
class ReuqestHandlerWithSessionId(object): class ReuqestHandlerWithSessionId(object):
...@@ -86,28 +89,26 @@ class ReuqestHandlerWithSessionId(object): ...@@ -86,28 +89,26 @@ class ReuqestHandlerWithSessionId(object):
return True return True
return False return False
def add_video_to_course(course, player_mode, hashes, display_name='Video'): def get_metadata(parent_location, player_mode, data, display_name='Video'):
category = 'video'
kwargs = { kwargs = {
'parent_location': section_location(course), 'parent_location': parent_location,
'category': category, 'category': 'video',
'display_name': display_name, 'display_name': display_name,
'metadata': {}, 'metadata': {},
} }
if hashes: if data:
kwargs['metadata'].update(hashes[0]) conversions = {
'transcripts': json.loads,
'download_track': json.loads,
'download_video': json.loads,
}
conversions = { for key in data:
'transcripts': json.loads, if key in conversions:
'download_track': json.loads, data[key] = conversions[key](data[key])
'download_video': json.loads,
}
for key in kwargs['metadata']: kwargs['metadata'].update(data)
if key in conversions:
kwargs['metadata'][key] = conversions[key](kwargs['metadata'][key])
if player_mode == 'html5': if player_mode == 'html5':
kwargs['metadata'].update({ kwargs['metadata'].update({
...@@ -134,19 +135,53 @@ def add_video_to_course(course, player_mode, hashes, display_name='Video'): ...@@ -134,19 +135,53 @@ def add_video_to_course(course, player_mode, hashes, display_name='Video'):
'html5_sources': HTML5_SOURCES_INCORRECT '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.scenario_dict['VIDEO'] = world.ItemFactory.create(**kwargs)
world.wait_for_present('.is-initialized') world.wait_for_present('.is-initialized')
world.wait_for_invisible('.video-wrapper .spinner') world.wait_for_invisible('.video-wrapper .spinner')
def _get_sjson_filename(videoId, lang): def add_vertical_to_course(course_num):
if lang == 'en': world.scenario_dict['LAST_VERTICAL'] = world.ItemFactory.create(
return 'subs_{0}.srt.sjson'.format(videoId) parent_location=world.scenario_dict['SECTION'].location,
else: category='vertical',
return '{0}_subs_{1}.srt.sjson'.format(lang, videoId) 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) path = os.path.join(TEST_ROOT, 'uploads/', filename)
f = open(os.path.abspath(path)) f = open(os.path.abspath(path))
mime_type = "application/json" mime_type = "application/json"
...@@ -159,27 +194,28 @@ def _upload_file(filename, location): ...@@ -159,27 +194,28 @@ def _upload_file(filename, location):
del_cached_content(content.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) sequence_css = '#sequence-list a[data-element="{0}"]'.format(number)
world.css_click(sequence_css) world.css_click(sequence_css)
def _change_video_speed(speed): def change_video_speed(speed):
world.browser.execute_script("$('.speeds').addClass('open')") world.browser.execute_script("$('.speeds').addClass('open')")
speed_css = 'li[data-speed="{0}"] a'.format(speed) speed_css = 'li[data-speed="{0}"] a'.format(speed)
world.css_click(speed_css) world.css_click(speed_css)
def _open_menu(menu):
def open_menu(menu):
world.browser.execute_script("$('{selector}').parent().addClass('open')".format( world.browser.execute_script("$('{selector}').parent().addClass('open')".format(
selector=VIDEO_MENUS[menu] selector=VIDEO_MENUS[menu]
)) ))
def _get_all_dimensions(): def get_all_dimensions():
video = _get_dimensions('.video-player iframe, .video-player video') video = get_dimensions('.video-player iframe, .video-player video')
wrapper = _get_dimensions('.tc-wrapper') wrapper = get_dimensions('.tc-wrapper')
controls = _get_dimensions('.video-controls') controls = get_dimensions('.video-controls')
progress_slider = _get_dimensions('.video-controls > .slider') progress_slider = get_dimensions('.video-controls > .slider')
expected = dict(wrapper) expected = dict(wrapper)
expected['height'] -= controls['height'] + 0.5 * progress_slider['height'] expected['height'] -= controls['height'] + 0.5 * progress_slider['height']
...@@ -187,30 +223,30 @@ def _get_all_dimensions(): ...@@ -187,30 +223,30 @@ def _get_all_dimensions():
return (video, expected) return (video, expected)
def _get_dimensions(selector): def get_dimensions(selector):
element = world.css_find(selector).first element = world.css_find(selector).first
return element._element.size return element._element.size
def _get_window_dimensions(): def get_window_dimensions():
return world.browser.driver.get_window_size() 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) world.browser.driver.set_window_size(width, height)
# Wait 200 ms when JS finish resizing # Wait 200 ms when JS finish resizing
world.wait(0.2) world.wait(0.2)
def _duration(): def duration():
""" """
Total duration of the video, in seconds. Total duration of the video, in seconds.
""" """
elapsed_time, duration = _video_time() elapsed_time, duration = video_time()
return duration return duration
def _video_time(): def video_time():
""" """
Return a tuple `(elapsed_time, duration)`, each in seconds. Return a tuple `(elapsed_time, duration)`, each in seconds.
""" """
...@@ -221,10 +257,10 @@ def _video_time(): ...@@ -221,10 +257,10 @@ def _video_time():
elapsed_str, duration_str = full_time.split(' / ') elapsed_str, duration_str = full_time.split(' / ')
# Convert each string to seconds # 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). Parse a string of the form 1:23 into seconds (int).
""" """
...@@ -237,23 +273,26 @@ def does_not_autoplay(_step, video_type): ...@@ -237,23 +273,26 @@ def does_not_autoplay(_step, video_type):
assert(world.css_find('.%s' % video_type)[0]['data-autoplay'] == 'False') 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): def view_video(_step, player_mode):
i_am_registered_for_the_course(_step, coursenum) 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') visit_scenario_item('SECTION')
@step('a video in "([^"]*)" mode(?:\:)?$') @step('a video in "([^"]*)" mode(?:\:)?$')
def add_video(_step, player_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') visit_scenario_item('SECTION')
@step('a video "([^"]*)" in "([^"]*)" mode in position "([^"]*)" of sequential(?:\:)?$') @step('video(?:s)? "([^"]*)" in "([^"]*)" mode in position "([^"]*)" of sequential(?:\:)?$')
def add_video_in_position(_step, player_id, player_mode, position): def add_video_in_position(_step, video_ids, player_mode, position):
sequence[player_id] = position sequences = {video_id.strip(): position for video_id in video_ids.split(',')}
add_video_to_course(coursenum, player_mode.lower(), _step.hashes, display_name=player_id) 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$') @step('I open the section with videos$')
...@@ -262,19 +301,19 @@ def visit_video_section(_step): ...@@ -262,19 +301,19 @@ def visit_video_section(_step):
@step('I select the "([^"]*)" speed$') @step('I select the "([^"]*)" speed$')
def change_video_speed(_step, speed): def i_select_video_speed(_step, speed):
_change_video_speed(speed) change_video_speed(speed)
@step('I select the "([^"]*)" speed on video "([^"]*)"$') @step('I select the "([^"]*)" speed on video "([^"]*)"$')
def change_video_speed_on_video(_step, speed, player_id): def change_video_speed_on_video(_step, speed, player_id):
_navigate_to_an_item_in_a_sequence(sequence[player_id]) navigate_to_an_item_in_a_sequence(world.video_sequences[player_id])
_change_video_speed(speed) change_video_speed(speed)
@step('I open video "([^"]*)"$') @step('I open video "([^"]*)"$')
def open_video(_step, player_id): 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 "([^"]*)"$') @step('video "([^"]*)" should start playing at speed "([^"]*)"$')
...@@ -288,7 +327,7 @@ def set_youtube_response_timeout(_step, time): ...@@ -288,7 +327,7 @@ def set_youtube_response_timeout(_step, time):
world.youtube.config['time_to_response'] = float(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): def video_is_rendered(_step, mode):
modes = { modes = {
'html5': 'video', 'html5': 'video',
...@@ -299,6 +338,20 @@ def video_is_rendered(_step, mode): ...@@ -299,6 +338,20 @@ def video_is_rendered(_step, mode):
assert world.is_css_present('.speed_link') 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$') @step('all sources are correct$')
def all_sources_are_correct(_step): def all_sources_are_correct(_step):
elements = world.css_find('.video-player video source') elements = world.css_find('.video-player video source')
...@@ -333,7 +386,7 @@ def set_captions_visibility_state(_step, captions_state): ...@@ -333,7 +386,7 @@ def set_captions_visibility_state(_step, captions_state):
@step('I see video menu "([^"]*)" with correct items$') @step('I see video menu "([^"]*)" with correct items$')
def i_see_menu(_step, menu): def i_see_menu(_step, menu):
_open_menu(menu) open_menu(menu)
menu_items = world.css_find(VIDEO_MENUS[menu] + ' li') menu_items = world.css_find(VIDEO_MENUS[menu] + ' li')
video = world.scenario_dict['VIDEO'] video = world.scenario_dict['VIDEO']
transcripts = dict(video.transcripts) transcripts = dict(video.transcripts)
...@@ -388,7 +441,7 @@ def start_playing_video_from_n_seconds(_step, position): ...@@ -388,7 +441,7 @@ def start_playing_video_from_n_seconds(_step, position):
@step('I see duration "([^"]*)"$') @step('I see duration "([^"]*)"$')
def i_see_duration(_step, position): def i_see_duration(_step, position):
world.wait_for( world.wait_for(
func=lambda _: _duration() == _parse_time_str(position), func=lambda _: duration() == parse_time_str(position),
timeout=5 timeout=5
) )
...@@ -402,7 +455,7 @@ def seek_video_to_n_seconds(_step, seconds): ...@@ -402,7 +455,7 @@ def seek_video_to_n_seconds(_step, seconds):
@step('I have a "([^"]*)" transcript file in assets$') @step('I have a "([^"]*)" transcript file in assets$')
def upload_to_assets(_step, filename): 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$') @step('button "([^"]*)" is hidden$')
...@@ -419,20 +472,20 @@ def is_hidden_menu(_step, menu): ...@@ -419,20 +472,20 @@ def is_hidden_menu(_step, menu):
def video_alignment(_step, transcript_visibility): def video_alignment(_step, transcript_visibility):
# Width of the video container in css equal 75% of window if transcript enabled # Width of the video container in css equal 75% of window if transcript enabled
wrapper_width = 75 if transcript_visibility == "with" else 100 wrapper_width = 75 if transcript_visibility == "with" else 100
initial = _get_window_dimensions() initial = get_window_dimensions()
_set_window_dimensions(300, 600) set_window_dimensions(300, 600)
real, expected = _get_all_dimensions() real, expected = get_all_dimensions()
width = round(100 * real['width']/expected['width']) == wrapper_width width = round(100 * real['width']/expected['width']) == wrapper_width
_set_window_dimensions(600, 300) set_window_dimensions(600, 300)
real, expected = _get_all_dimensions() real, expected = get_all_dimensions()
height = abs(expected['height'] - real['height']) <= 5 height = abs(expected['height'] - real['height']) <= 5
# Restore initial window size # Restore initial window size
_set_window_dimensions( set_window_dimensions(
initial['width'], initial['height'] initial['width'], initial['height']
) )
...@@ -473,3 +526,12 @@ def select_transcript_format(_step, format): ...@@ -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_find(menu_selector + ' .active a')[0]['data-value'] == format
assert world.css_has_text(button_selector, '.' + format, strip=True) 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')
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment