Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
bd4be00a
Commit
bd4be00a
authored
Mar 17, 2014
by
polesye
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
BLD-534: Add acceptance test.
parent
982f24ae
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
162 additions
and
73 deletions
+162
-73
lms/djangoapps/courseware/features/video.feature
+42
-15
lms/djangoapps/courseware/features/video.py
+120
-58
No files found.
lms/djangoapps/courseware/features/video.feature
View file @
bd4be00a
...
...
@@ -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,8 +15,8 @@ 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
...
...
@@ -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
lms/djangoapps/courseware/features/video.py
View file @
bd4be00a
# -*- 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
,
}
for
key
in
kwargs
[
'metadata'
]
:
for
key
in
data
:
if
key
in
conversions
:
kwargs
[
'metadata'
][
key
]
=
conversions
[
key
](
kwargs
[
'metadata'
][
key
])
data
[
key
]
=
conversions
[
key
](
data
[
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'
)
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment