Files
edx-platform/common/test/acceptance/tests/video/test_video_events.py
2020-03-04 15:06:54 -05:00

190 lines
7.4 KiB
Python

"""Ensure videos emit proper events"""
import datetime
import json
import six
from opaque_keys.edx.keys import CourseKey, UsageKey
from common.test.acceptance.tests.helpers import EventsTestMixin
from common.test.acceptance.tests.video.test_video_module import VideoBaseTest
from openedx.core.lib.tests.assertions.events import assert_event_matches, assert_events_equal
class VideoEventsTestMixin(EventsTestMixin, VideoBaseTest):
"""
Useful helper methods to test video player event emission.
"""
def assert_payload_contains_ids(self, video_event):
"""
Video events should all contain "id" and "code" attributes in their payload.
This function asserts that those fields are present and have correct values.
"""
video_descriptors = self.course_fixture.get_nested_xblocks(category='video')
video_desc = video_descriptors[0]
video_locator = UsageKey.from_string(video_desc.locator)
expected_event = {
'event': {
'id': video_locator.html_id(),
'code': '3_yD_cEKoCk'
}
}
self.assert_events_match([expected_event], [video_event])
def assert_valid_control_event_at_time(self, video_event, time_in_seconds):
"""
Video control events should contain valid ID fields and a valid "currentTime" field.
This function asserts that those fields are present and have correct values.
"""
current_time = json.loads(video_event['event'])['currentTime']
self.assertAlmostEqual(current_time, time_in_seconds, delta=1)
def assert_field_type(self, event_dict, field, field_type):
"""Assert that a particular `field` in the `event_dict` has a particular type"""
self.assertIn(field, event_dict, u'{0} not found in the root of the event'.format(field))
self.assertTrue(
isinstance(event_dict[field], field_type),
u'Expected "{key}" to be a "{field_type}", but it has the value "{value}" of type "{t}"'.format(
key=field,
value=event_dict[field],
t=type(event_dict[field]),
field_type=field_type,
)
)
class VideoEventsTest(VideoEventsTestMixin):
""" Test video player event emission """
shard = 21
def test_video_control_events(self):
"""
Scenario: Video component is rendered in the LMS in Youtube mode without HTML5 sources
Given the course has a Video component in "Youtube" mode
And I play the video
And I watch 5 seconds of it
And I pause the video
Then a "load_video" event is emitted
And a "play_video" event is emitted
And a "pause_video" event is emitted
"""
def is_video_event(event):
"""Filter out anything other than the video events of interest"""
return event['event_type'] in ('load_video', 'play_video', 'pause_video')
captured_events = []
with self.capture_events(is_video_event, number_of_matches=3, captured_events=captured_events):
self.navigate_to_video()
self.video.click_player_button('play')
self.video.wait_for_position('0:05')
self.video.click_player_button('pause')
for idx, video_event in enumerate(captured_events):
self.assert_payload_contains_ids(video_event)
if idx == 0:
assert_event_matches({'event_type': 'load_video'}, video_event)
elif idx == 1:
assert_event_matches({'event_type': 'play_video'}, video_event)
self.assert_valid_control_event_at_time(video_event, 0)
elif idx == 2:
assert_event_matches({'event_type': 'pause_video'}, video_event)
self.assert_valid_control_event_at_time(video_event, self.video.seconds)
def test_strict_event_format(self):
"""
This test makes a very strong assertion about the fields present in events. The goal of it is to ensure that new
fields are not added to all events mistakenly. It should be the only existing test that is updated when new top
level fields are added to all events.
"""
captured_events = []
with self.capture_events(lambda e: e['event_type'] == 'load_video', captured_events=captured_events):
self.navigate_to_video()
load_video_event = captured_events[0]
# Validate the event payload
self.assert_payload_contains_ids(load_video_event)
# We cannot predict the value of these fields so we make weaker assertions about them
dynamic_string_fields = (
'accept_language',
'agent',
'host',
'ip',
'event',
'session'
)
for field in dynamic_string_fields:
self.assert_field_type(load_video_event, field, six.string_types)
self.assertIn(field, load_video_event, u'{0} not found in the root of the event'.format(field))
del load_video_event[field]
# A weak assertion for the timestamp as well
self.assert_field_type(load_video_event, 'time', datetime.datetime)
del load_video_event['time']
# Note that all unpredictable fields have been deleted from the event at this point
course_key = CourseKey.from_string(self.course_id)
static_fields_pattern = {
'context': {
'course_id': six.text_type(course_key),
'org_id': course_key.org,
'path': '/event',
'user_id': self.user_info['user_id']
},
'event_source': 'browser',
'event_type': 'load_video',
'username': self.user_info['username'],
'page': self.browser.current_url,
'referer': self.browser.current_url,
'name': 'load_video',
}
assert_events_equal(static_fields_pattern, load_video_event)
class VideoHLSEventsTest(VideoEventsTestMixin):
"""
Test video player event emission for HLS video
"""
shard = 19
def test_event_data_for_hls(self):
"""
Scenario: Video component with HLS video emits events correctly
Given the course has a Video component with Youtube, HTML5 and HLS sources available.
And I play the video
And the video starts playing
And I watch 3 seconds of it
When I pause and seek the video
And I play the video to the end
Then I verify that all expected events are triggered
And triggered events have correct data
"""
video_events = ('load_video', 'play_video', 'pause_video', 'seek_video')
def is_video_event(event):
"""
Filter out anything other than the video events of interest
"""
return event['event_type'] in video_events
captured_events = []
with self.capture_events(is_video_event, captured_events=captured_events):
self.metadata = self.metadata_for_mode('hls')
self.navigate_to_video()
self.video.click_player_button('play')
self.video.wait_for_position('0:03')
self.video.click_player_button('pause')
self.video.seek('0:08')
expected_events = [{'name': event, 'event': {'code': 'hls'}} for event in video_events]
self.assert_events_match(expected_events, captured_events)