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