1# Copyright (c) 2013 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import logging
6import operator
7import os
8import time
9
10from autotest_lib.client.bin import test
11from autotest_lib.client.common_lib import error
12from autotest_lib.client.common_lib.cros import chrome
13
14
15class video_YouTubePage(test.test):
16    """The main test class of this test.
17
18    """
19
20
21    version = 1
22
23    PSEUDO_RANDOM_TIME_1 = 20.25
24    PSEUDO_RANDOM_TIME_2 = 5.47
25
26    # Minimum number of timeupdates required to fire in the last second.
27    MIN_LAST_SECOND_UPDATES = 3
28
29    NO_DELAY = 0
30    MINIMAL_DELAY = 1
31    MAX_REBUFFER_DELAY = 10
32
33    PLAYING_STATE = 'playing'
34    PAUSED_STATE = 'paused'
35    ENDED_STATE = 'ended'
36    TEST_PAGE = 'http://web-release-qa.youtube.com/watch?v=zuzaxlddWbk&html5=1'
37
38    DISABLE_COOKIES = False
39
40    tab = None
41
42
43    def initialize_test(self, chrome, player_page):
44        """Initializes the test.
45
46        @param chrome: An Autotest Chrome instance.
47        @param player_page: The URL (string) of the YouTube player page to test.
48
49        """
50        self.tab = chrome.browser.tabs[0]
51
52        self.tab.Navigate(player_page)
53        self.tab.WaitForDocumentReadyStateToBeComplete()
54        time.sleep(2)
55
56        with open(
57                os.path.join(os.path.dirname(__file__),
58                'files/video_YouTubePageCommon.js')) as f:
59            js = f.read()
60            if not self.tab.EvaluateJavaScript(js):
61                raise error.TestFail('YouTube page failed to load.')
62            logging.info('Loaded accompanying .js script.')
63
64
65    def get_player_state(self):
66        """Simple wrapper to get the JS player state.
67
68        @returns: The state of the player (string).
69
70        """
71        return self.tab.EvaluateJavaScript('window.__getVideoState();')
72
73
74    def play_video(self):
75        """Simple wrapper to play the video.
76
77        """
78        self.tab.ExecuteJavaScript('window.__playVideo();')
79
80
81    def pause_video(self):
82        """Simple wrapper to pause the video.
83
84        """
85        self.tab.ExecuteJavaScript('window.__pauseVideo();')
86
87
88    def seek_video(self, new_time):
89        """Simple wrapper to seek the video to a new time.
90
91        @param new_time: Time to seek to.
92
93        """
94        self.tab.ExecuteJavaScript('window.__seek(%f);' % new_time)
95
96
97    def seek_to_almost_end(self, seconds_before_end):
98        """Simple wrapper to seek to almost the end of the video.
99
100        @param seconds_before_end: How many seconds (a float, not integer)
101                before end of video.
102
103        """
104        self.tab.ExecuteJavaScript(
105                'window.__seekToAlmostEnd(%f);' % seconds_before_end)
106
107
108    def get_current_time(self):
109        """Simple wrapper to get the current time in the video.
110
111        @returns: The current time (float).
112
113        """
114        return self.tab.EvaluateJavaScript('window.__getCurrentTime();')
115
116
117    def assert_event_state(self, event, op, error_str):
118        """Simple wrapper to get the status of a state in the video.
119
120        @param event: A string denoting the event. Check the accompanying JS
121                file for the possible values.
122        @param op: truth or not_ operator from the standard Python operator
123                module.
124        @param error_str: A string for the error output.
125
126        @returns: Whether or not the input event has fired.
127
128        """
129        result = self.tab.EvaluateJavaScript(
130                'window.__getEventHappened("%s");' % event)
131        if not op(result):
132            raise error.TestError(error)
133
134
135    def clear_event_state(self, event):
136        """Simple wrapper to clear the status of a state in the video.
137
138        @param event: A string denoting the event. Check the accompanying JS
139                file for the possible vlaues.
140
141        """
142        self.tab.ExecuteJavaScript('window.__clearEventHappened("%s");' % event)
143
144
145    def verify_last_second_playback(self):
146        """Simple wrapper to check the playback of the last second.
147
148        """
149        result = self.tab.EvaluateJavaScript(
150                'window.__getLastSecondTimeupdates()')
151        if result < self.MIN_LAST_SECOND_UPDATES:
152            raise error.TestError(
153                    'Last second did not play back correctly (%d events).' %
154                    result)
155
156
157    def assert_player_state(self, state, max_wait_secs):
158        """Simple wrapper to busy wait and test the current state of the player.
159
160        @param state: A string denoting the expected state of the player.
161        @param max_wait_secs: Maximum amount of time to wait before failing.
162
163        @raises: A error.TestError if the state is not as expected.
164
165        """
166        start_time = time.time()
167        while True:
168            current_state = self.get_player_state()
169            if current_state == state:
170                return
171            elif time.time() < start_time + max_wait_secs:
172                time.sleep(0.5)
173            else:
174                raise error.TestError(
175                        'Current player state "%s" is not the expected state '
176                        '"%s".' % (current_state, state))
177
178
179    def perform_test(self):
180        """Base method for derived classes to run their test.
181
182        """
183        raise error.TestFail('Derived class did not specify a perform_test.')
184
185
186    def perform_playing_test(self):
187        """Test to check if the YT page starts off playing.
188
189        """
190        self.assert_player_state(self.PLAYING_STATE, self.NO_DELAY)
191        if self.get_current_time() <= 0.0:
192            raise error.TestError('perform_playing_test failed.')
193
194
195    def perform_pausing_test(self):
196        """Test to check if the video is in the 'paused' state.
197
198        """
199        self.assert_player_state(self.PLAYING_STATE, self.NO_DELAY)
200        self.pause_video()
201        self.assert_player_state(self.PAUSED_STATE, self.MINIMAL_DELAY)
202
203
204    def perform_resuming_test(self):
205        """Test to check if the video responds to resumption.
206
207        """
208        self.assert_player_state(self.PLAYING_STATE, self.NO_DELAY)
209        self.pause_video()
210        self.assert_player_state(self.PAUSED_STATE, self.MINIMAL_DELAY)
211        self.play_video()
212        self.assert_player_state(self.PLAYING_STATE, self.MINIMAL_DELAY)
213
214
215    def perform_seeking_test(self):
216        """Test to check if seeking works.
217
218        """
219        # Test seeking while playing.
220        self.assert_player_state(self.PLAYING_STATE, self.NO_DELAY)
221        self.seek_video(self.PSEUDO_RANDOM_TIME_1)
222        time.sleep(self.MINIMAL_DELAY)
223        if not self.tab.EvaluateJavaScript(
224                'window.__getCurrentTime() >= %f;' % self.PSEUDO_RANDOM_TIME_1):
225            raise error.TestError(
226                    'perform_seeking_test failed because player time is not '
227                    'the expected time during playing seeking.')
228        self.assert_event_state(
229                'seeking', operator.truth,
230                'perform_seeking_test failed: "seeking" state did not fire.')
231        self.assert_event_state(
232                'seeked', operator.truth,
233                'perform_seeking_test failed: "seeked" state did not fire.')
234
235        # Now make sure the video is still playing.
236
237        # Let it buffer/play for at most 10 seconds before continuing.
238        self.assert_player_state(self.PLAYING_STATE, self.MAX_REBUFFER_DELAY)
239
240        self.clear_event_state('seeking');
241        self.clear_event_state('seeked');
242        self.assert_event_state(
243                'seeking', operator.not_,
244                'perform_seeking_test failed: '
245                '"seeking" state did not get cleared.')
246        self.assert_event_state(
247                'seeked', operator.not_,
248                'perform_seeking_test failed: '
249                '"seeked" state did not get cleared.')
250
251        # Test seeking while paused.
252        self.pause_video()
253        self.assert_player_state(self.PAUSED_STATE, self.MINIMAL_DELAY)
254
255        self.seek_video(self.PSEUDO_RANDOM_TIME_2)
256        time.sleep(self.MINIMAL_DELAY)
257        if not self.tab.EvaluateJavaScript(
258                'window.__getCurrentTime() === %f;' %
259                self.PSEUDO_RANDOM_TIME_2):
260            raise error.TestError(
261                    'perform_seeking_test failed because player time is not '
262                    'the expected time.')
263        self.assert_event_state(
264                'seeking', operator.truth,
265                'perform_seeking_test failed: "seeking" state did not fire '
266                'again.')
267        self.assert_event_state(
268                'seeked', operator.truth,
269                'perform_seeking_test failed: "seeked" state did not fire '
270                'again.')
271
272        # Make sure the video is paused.
273        self.assert_player_state(self.PAUSED_STATE, self.NO_DELAY)
274
275
276    def perform_frame_drop_test(self):
277        """Test to check if there are too many dropped frames.
278
279        """
280        self.assert_player_state(self.PLAYING_STATE, self.NO_DELAY)
281        time.sleep(15)
282        dropped_frames_percentage = self.tab.EvaluateJavaScript(
283                'window.__videoElement.webkitDroppedFrameCount /'
284                'window.__videoElement.webkitDecodedFrameCount')
285        if dropped_frames_percentage > 0.01:
286            raise error.TestError((
287                    'perform_frame_drop_test failed due to too many dropped '
288                    'frames (%f%%)') % (dropped_frames_percentage * 100))
289
290
291    def perform_ending_test(self):
292        """Test to check if the state is 'ended' at the end of a video.
293
294        """
295        ALMOST_END = 0.1
296        self.assert_player_state(self.PLAYING_STATE, self.NO_DELAY)
297        self.seek_to_almost_end(ALMOST_END)
298        self.assert_player_state(self.ENDED_STATE, self.MAX_REBUFFER_DELAY)
299
300
301    def perform_last_second_test(self):
302        """Test to check if the last second is played.
303
304        """
305        NEAR_END = 2.0
306        self.assert_player_state(self.PLAYING_STATE, self.NO_DELAY)
307        self.seek_to_almost_end(NEAR_END)
308        self.assert_player_state(
309                self.ENDED_STATE, self.MAX_REBUFFER_DELAY + NEAR_END)
310        self.verify_last_second_playback()
311
312
313    def run_once(self, subtest_name):
314        """Main runner for the test.
315
316        @param subtest_name: The name of the test to run, given below.
317
318        """
319        extension_paths = []
320        if self.DISABLE_COOKIES:
321            # To stop the system from erasing the previous profile, enable:
322            #  options.dont_override_profile = True
323            extension_path = os.path.join(
324                    os.path.dirname(__file__),
325                    'files/cookie-disabler')
326            extension_paths.append(extension_path)
327
328
329        with chrome.Chrome(extension_paths=extension_paths) as cr:
330            self.initialize_test(cr, self.TEST_PAGE)
331
332            if subtest_name is 'playing':
333                self.perform_playing_test()
334            elif subtest_name is 'pausing':
335                self.perform_pausing_test()
336            elif subtest_name is 'resuming':
337                self.perform_resuming_test()
338            elif subtest_name is 'seeking':
339                self.perform_seeking_test()
340            elif subtest_name is 'frame_drop':
341                self.perform_frame_drop_test()
342            elif subtest_name is 'ending':
343                self.perform_ending_test()
344            elif subtest_name is 'last_second':
345                self.perform_last_second_test()
346