""" Acceptance tests for Video. """ import os from unittest import skipIf from unittest.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.lms.courseware import CoursewarePage from common.test.acceptance.pages.lms.tab_nav import TabNavPage from common.test.acceptance.pages.lms.video.video import VideoPage from common.test.acceptance.tests.helpers import ( UniqueCourseTest, YouTubeStubConfig, is_youtube_available, ) from openedx.core.lib.tests import attr VIDEO_SOURCE_PORT = 8777 VIDEO_HOSTNAME = os.environ.get('BOK_CHOY_HOSTNAME', 'localhost') HTML5_SOURCES = [ f'http://{VIDEO_HOSTNAME}:{VIDEO_SOURCE_PORT}/gizmo.mp4', f'http://{VIDEO_HOSTNAME}:{VIDEO_SOURCE_PORT}/gizmo.webm', f'http://{VIDEO_HOSTNAME}:{VIDEO_SOURCE_PORT}/gizmo.ogv', ] HTML5_SOURCES_INCORRECT = [ f'http://{VIDEO_HOSTNAME}:{VIDEO_SOURCE_PORT}/gizmo.mp99', ] HLS_SOURCES = [ f'http://{VIDEO_HOSTNAME}:{VIDEO_SOURCE_PORT}/hls/history.m3u8', ] @skipIf(is_youtube_available() is False, 'YouTube is not available!') class VideoBaseTest(UniqueCourseTest): """ Base class for tests of the Video Player Sets up the course and provides helper functions for the Video tests. """ def setUp(self): """ Initialization of pages and course fixture for video tests """ super().setUp() self.longMessage = True self.video = VideoPage(self.browser) self.tab_nav = TabNavPage(self.browser) self.courseware_page = CoursewarePage(self.browser, self.course_id) self.auth_page = AutoAuthPage(self.browser, course_id=self.course_id) self.course_fixture = CourseFixture( self.course_info['org'], self.course_info['number'], self.course_info['run'], self.course_info['display_name'] ) self.metadata = None self.assets = [] self.contents_of_verticals = None self.youtube_configuration = {} self.user_info = {} # reset youtube stub server self.addCleanup(YouTubeStubConfig.reset) def navigate_to_video(self): """ Prepare the course and get to the video and render it """ self._install_course_fixture() self._navigate_to_courseware_video_and_render() def navigate_to_video_no_render(self): """ Prepare the course and get to the video unit however do not wait for it to render, because the has been an error. """ self._install_course_fixture() self._navigate_to_courseware_video_no_render() def _install_course_fixture(self): """ Install the course fixture that has been defined """ if self.assets: self.course_fixture.add_asset(self.assets) chapter_sequential = XBlockFixtureDesc('sequential', 'Test Section') chapter_sequential.add_children(*self._add_course_verticals()) chapter = XBlockFixtureDesc('chapter', 'Test Chapter').add_children(chapter_sequential) self.course_fixture.add_children(chapter) self.course_fixture.install() if len(self.youtube_configuration) > 0: YouTubeStubConfig.configure(self.youtube_configuration) def _add_course_verticals(self): """ Create XBlockFixtureDesc verticals :return: a list of XBlockFixtureDesc """ xblock_verticals = [] _contents_of_verticals = self.contents_of_verticals # Video tests require at least one vertical with a single video. if not _contents_of_verticals: _contents_of_verticals = [[{'display_name': 'Video', 'metadata': self.metadata}]] for vertical_index, vertical in enumerate(_contents_of_verticals): xblock_verticals.append(self._create_single_vertical(vertical, vertical_index)) return xblock_verticals def _create_single_vertical(self, vertical_contents, vertical_index): """ Create a single course vertical of type XBlockFixtureDesc with category `vertical`. A single course vertical can contain single or multiple video modules. :param vertical_contents: a list of items for the vertical to contain :param vertical_index: index for the vertical display name :return: XBlockFixtureDesc """ xblock_course_vertical = XBlockFixtureDesc('vertical', f'Test Vertical-{vertical_index}') for video in vertical_contents: xblock_course_vertical.add_children( XBlockFixtureDesc('video', video['display_name'], metadata=video.get('metadata'))) return xblock_course_vertical def _navigate_to_courseware_video(self): """ Register for the course and navigate to the video unit """ self.auth_page.visit() self.user_info = self.auth_page.user_info self.courseware_page.visit() def _navigate_to_courseware_video_and_render(self): """ Wait for the video player to render """ self._navigate_to_courseware_video() self.video.wait_for_video_player_render() def _navigate_to_courseware_video_no_render(self): """ Wait for the video Xmodule but not for rendering """ self._navigate_to_courseware_video() self.video.wait_for_video_class() def metadata_for_mode(self, player_mode, additional_data=None): """ Create a dictionary for video player configuration according to `player_mode` :param player_mode (str): Video player mode :param additional_data (dict): Optional additional metadata. :return: dict """ metadata = {} youtube_ids = { 'youtube_id_1_0': '', 'youtube_id_0_75': '', 'youtube_id_1_25': '', 'youtube_id_1_5': '', } if player_mode == 'html5': metadata.update(youtube_ids) metadata.update({ 'html5_sources': HTML5_SOURCES }) if player_mode == 'youtube_html5': metadata.update({ 'html5_sources': HTML5_SOURCES, }) if player_mode == 'youtube_html5_unsupported_video': metadata.update({ 'html5_sources': HTML5_SOURCES_INCORRECT }) if player_mode == 'html5_unsupported_video': metadata.update(youtube_ids) metadata.update({ 'html5_sources': HTML5_SOURCES_INCORRECT }) if player_mode == 'hls': metadata.update(youtube_ids) metadata.update({ 'html5_sources': HLS_SOURCES, }) if player_mode == 'html5_and_hls': metadata.update(youtube_ids) metadata.update({ 'html5_sources': HTML5_SOURCES + HLS_SOURCES, }) if additional_data: metadata.update(additional_data) return metadata def go_to_sequential_position(self, position): """ Navigate to sequential specified by `video_display_name` """ self.courseware_page.go_to_sequential_position(position) self.video.wait_for_video_player_render() @attr('a11y') class LMSVideoBlockA11yTest(VideoBaseTest): """ LMS Video Accessibility Test Class """ 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().setUp() def test_video_player_a11y(self): # load transcripts so we can test skipping to self.assets.extend(['english_single_transcript.srt', 'subs_3_yD_cEKoCk.srt.sjson']) data = {'transcripts': {"en": "english_single_transcript.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_captions() # limit the scope of the audit to the video player only. self.video.a11y_audit.config.set_scope( include=["div.video"] ) self.video.a11y_audit.check_for_accessibility_errors()