video.py 8.44 KB
Newer Older
1
# pylint: disable=C0111
2 3

from lettuce import world, step
4
from selenium.webdriver.common.keys import Keys
5
from xmodule.modulestore.django import modulestore
6

7
VIDEO_BUTTONS = {
polesye committed
8 9
    'CC': '.hide-subtitles',
    'volume': '.volume',
polesye committed
10 11
    'play': '.video_control.play',
    'pause': '.video_control.pause',
polesye committed
12
    'handout': '.video-handout.video-download-button a',
polesye committed
13 14
}

15 16 17 18 19
SELECTORS = {
    'spinner': '.video-wrapper .spinner',
    'controls': 'section.video-controls',
}

20 21 22
# We should wait 300 ms for event handler invocation + 200ms for safety.
DELAY = 0.5

23 24 25 26 27 28 29 30 31 32
@step('youtube stub server (.*) YouTube API')
def configure_youtube_api(_step, action):
    action=action.strip()
    if action == 'proxies':
        world.youtube.config['youtube_api_blocked'] = False
    elif action == 'blocks':
        world.youtube.config['youtube_api_blocked'] = True
    else:
        raise ValueError('Parameter `action` should be one of "proxies" or "blocks".')

33
@step('I have created a Video component$')
34 35
def i_created_a_video_component(step):
    step.given('I am in Studio editing a new unit')
36
    world.create_component_instance(
37
        step=step,
38
        category='video',
39 40
    )

41 42 43 44 45
    world.wait_for_xmodule()
    world.disable_jquery_animations()

    world.wait_for_present('.is-initialized')
    world.wait(DELAY)
46
    world.wait_for_invisible(SELECTORS['spinner'])
47 48
    if not world.youtube.config.get('youtube_api_blocked'):
        world.wait_for_visible(SELECTORS['controls'])
49

polesye committed
50 51 52 53
@step('I have created a Video component with subtitles$')
def i_created_a_video_with_subs(_step):
    _step.given('I have created a Video component with subtitles "OEoXaMPEzfM"')

polesye committed
54

polesye committed
55 56
@step('I have created a Video component with subtitles "([^"]*)"$')
def i_created_a_video_with_subs_with_name(_step, sub_id):
polesye committed
57
    _step.given('I have created a Video component')
58 59 60 61 62

    # Store the current URL so we can return here
    video_url = world.browser.url

    # Upload subtitles for the video using the upload interface
polesye committed
63
    _step.given('I have uploaded subtitles "{}"'.format(sub_id))
64 65 66

    # Return to the video
    world.visit(video_url)
67

68
    world.wait_for_xmodule()
69 70 71 72 73 74 75

    # update .sub filed with proper subs name (which mimics real Studio/XML behavior)
    # this is needed only for that videos which are created in acceptance tests.
    _step.given('I edit the component')
    world.wait_for_ajax_complete()
    _step.given('I save changes')

76 77 78
    world.disable_jquery_animations()

    world.wait_for_present('.is-initialized')
79
    world.wait_for_invisible(SELECTORS['spinner'])
80 81


polesye committed
82
@step('I have uploaded subtitles "([^"]*)"$')
polesye committed
83 84
def i_have_uploaded_subtitles(_step, sub_id):
    _step.given('I go to the files and uploads page')
85
    _step.given('I upload the test file "subs_{}.srt.sjson"'.format(sub_id.strip()))
86 87


Anton Stupak committed
88
@step('when I view the (.*) it does not have autoplay enabled$')
89
def does_not_autoplay(_step, video_type):
90 91
    world.wait(DELAY)
    world.wait_for_ajax_complete()
polesye committed
92 93 94
    actual = world.css_find('.%s' % video_type)[0]['data-autoplay']
    expected = [u'False', u'false', False]
    assert actual in expected
95
    assert world.css_has_class('.video_control', 'play')
96 97


Anton Stupak committed
98
@step('creating a video takes a single click$')
Don Mitchell committed
99
def video_takes_a_single_click(_step):
100 101 102
    component_css = '.xmodule_VideoModule'
    assert world.is_css_not_present(component_css)

103
    world.css_click("a[data-category='video']")
104
    assert world.is_css_present(component_css)
105 106


Anton Stupak committed
107
@step('I edit the component$')
108 109 110 111
def i_edit_the_component(_step):
    world.edit_component()


Anton Stupak committed
112
@step('I have (hidden|toggled) captions$')
113
def hide_or_show_captions(step, shown):
114
    button_css = 'a.hide-subtitles'
115
    if shown == 'hidden':
116 117 118 119 120 121 122
        world.css_click(button_css)
    if shown == 'toggled':
        world.css_click(button_css)
        # When we click the first time, a tooltip shows up. We want to
        # click the button rather than the tooltip, so move the mouse
        # away to make it disappear.
        button = world.css_find(button_css)
123
        # mouse_out is not implemented on firefox with selenium
124
        if not world.is_firefox:
125
            button.mouse_out()
126
        world.css_click(button_css)
Peter Fogg committed
127

128

Anton Stupak committed
129
@step('I have created a video with only XML data$')
130 131 132 133 134 135 136 137
def xml_only_video(step):
    # Create a new video *without* metadata. This requires a certain
    # amount of rummaging to make sure all the correct data is present
    step.given('I have clicked the new unit button')

    # Wait for the new unit to be created and to load the page
    world.wait(1)

138
    course = world.scenario_dict['COURSE']
139
    store = modulestore()
140

141
    parent_location = store.get_items(course.id, category='vertical')[0].location
142 143 144 145 146 147 148

    youtube_id = 'ABCDEFG'
    world.scenario_dict['YOUTUBE_ID'] = youtube_id

    # Create a new Video component, but ensure that it doesn't have
    # metadata. This allows us to test that we are correctly parsing
    # out XML
149
    world.ItemFactory.create(
150 151
        parent_location=parent_location,
        category='video',
152 153
        data='<video youtube="1.00:%s"></video>' % youtube_id,
        modulestore=store,
154
        user_id=world.scenario_dict["USER"].id
155 156 157
    )


Anton Stupak committed
158
@step('The correct Youtube video is shown$')
159 160
def the_youtube_video_is_shown(_step):
    ele = world.css_find('.video').first
161
    assert ele['data-streams'].split(':')[1] == world.scenario_dict['YOUTUBE_ID']
Valera Rozuvan committed
162 163 164


@step('Make sure captions are (.+)$')
165
def set_captions_visibility_state(_step, captions_state):
166
    SELECTOR = '.closed .subtitles'
167
    world.wait_for_visible('.hide-subtitles')
Valera Rozuvan committed
168
    if captions_state == 'closed':
169
        if world.is_css_not_present(SELECTOR):
170
            world.css_find('.hide-subtitles').click()
Valera Rozuvan committed
171
    else:
172
        if world.is_css_present(SELECTOR):
173
            world.css_find('.hide-subtitles').click()
Valera Rozuvan committed
174 175


polesye committed
176
@step('I hover over button "([^"]*)"$')
177
def hover_over_button(_step, button):
178
    world.css_find(VIDEO_BUTTONS[button.strip()]).mouse_over()
polesye committed
179

Valera Rozuvan committed
180

polesye committed
181
@step('Captions (?:are|become) "([^"]*)"$')
182
def check_captions_visibility_state(_step, visibility_state):
Valera Rozuvan committed
183 184 185 186
    if visibility_state == 'visible':
        assert world.css_visible('.subtitles')
    else:
        assert not world.css_visible('.subtitles')
187 188 189 190 191 192 193


def find_caption_line_by_data_index(index):
    SELECTOR = ".subtitles > li[data-index='{index}']".format(index=index)
    return world.css_find(SELECTOR).first


194
@step('I focus on caption line with data-index "([^"]*)"$')
195
def focus_on_caption_line(_step, index):
196 197
    world.wait_for_present('.video.is-captions-rendered')
    world.wait_for(lambda _: world.css_text('.subtitles'), timeout=30)
198 199 200
    find_caption_line_by_data_index(int(index.strip()))._element.send_keys(Keys.TAB)


201 202
@step('I press "enter" button on caption line with data-index "([^"]*)"$')
def click_on_the_caption(_step, index):
203 204
    world.wait_for_present('.video.is-captions-rendered')
    world.wait_for(lambda _: world.css_text('.subtitles'), timeout=30)
205 206 207
    find_caption_line_by_data_index(int(index.strip()))._element.send_keys(Keys.ENTER)


208
@step('I see caption line with data-index "([^"]*)" has class "([^"]*)"$')
209 210
def caption_line_has_class(_step, index, className):
    SELECTOR = ".subtitles > li[data-index='{index}']".format(index=int(index.strip()))
211
    assert world.css_has_class(SELECTOR, className.strip())
212

213

polesye committed
214 215 216 217
@step('I see a range on slider$')
def see_a_range_slider_with_proper_range(_step):
    world.wait_for_visible(VIDEO_BUTTONS['pause'])
    assert world.css_visible(".slider-range")
218

219

220 221 222 223 224 225 226 227 228 229 230 231 232 233
@step('I (.*) see video button "([^"]*)"$')
def do_not_see_or_not_button_video(_step, action, button_type):
    world.wait(DELAY)
    world.wait_for_ajax_complete()
    action=action.strip()
    button = button_type.strip()
    if action == 'do not':
        assert not world.is_css_present(VIDEO_BUTTONS[button])
    elif action == 'can':
        assert world.css_visible(VIDEO_BUTTONS[button])
    else:
        raise ValueError('Parameter `action` should be one of "do not" or "can".')


234 235 236 237 238 239 240
@step('I click video button "([^"]*)"$')
def click_button_video(_step, button_type):
    world.wait(DELAY)
    world.wait_for_ajax_complete()
    button = button_type.strip()
    world.css_click(VIDEO_BUTTONS[button])

241 242 243 244 245 246 247 248 249 250 251 252 253 254

@step('I seek video to "([^"]*)" seconds$')
def seek_video_to_n_seconds(_step, seconds):
    time = float(seconds.strip())
    jsCode = "$('.video').data('video-player-state').videoPlayer.onSlideSeek({{time: {0:f}}})".format(time)
    world.browser.execute_script(jsCode)


@step('I see video starts playing from "([^"]*)" position$')
def start_playing_video_from_n_seconds(_step, position):
    world.wait_for(
        func=lambda _: world.css_html('.vidtime')[:4] == position.strip(),
        timeout=5
    )