new file mode 100644
--- /dev/null
+++ b/dom/media/test/external-media-tests/MANIFEST.in
@@ -0,0 +1,7 @@
+exclude MANIFEST.in
+include requirements.txt
+include setup.py
+recursive-include firefox_media_tests *
+recursive-include media_utils *
+
+
new file mode 100644
--- /dev/null
+++ b/dom/media/test/external-media-tests/README.md
@@ -0,0 +1,157 @@
+firefox-media-tests
+===================
+
+[Marionette Python tests][marionette-python-tests] for media playback in Mozilla Firefox. MediaTestCase uses [Firefox Puppeteer][ff-puppeteer-docs] library.
+
+Setup
+-----
+
+The instructions below assume you have a copy of the project in `some/path/firefox-media-tests` and they refer to this path as `$PROJECT_HOME`.
+
+* Create a virtualenv called `foo`.
+
+ ```sh
+ $ virtualenv foo
+ $ source foo/bin/activate #or `foo\Scripts\activate` on Windows
+ ```
+
+* Install `firefox-media-tests` in development mode. (To get an environment that is closer to what is actually used in Mozilla's automation jobs, run `pip install -r requirements.txt` first.)
+
+ ```sh
+ $ python setup.py develop
+ ```
+
+Now `firefox-media-tests` should be a recognized command. Try `firefox-media-tests --help` to see if it works.
+
+
+Running the Tests
+-----------------
+
+In the examples below, `$FF_PATH` is a path to a recent Firefox binary.
+
+This runs all the tests listed in `$PROJECT_HOME/firefox_media_tests/manifest.ini`:
+
+ ```sh
+ $ firefox-media-tests --binary $FF_PATH
+ ```
+
+You can also run all the tests at a particular path:
+
+ ```sh
+ $ firefox-media-tests --binary $FF_PATH some/path/foo
+ ```
+
+Or you can run the tests that are listed in a manifest file of your choice.
+
+ ```sh
+ $ firefox-media-tests --binary $FF_PATH some/other/path/manifest.ini
+ ```
+
+By default, the urls listed in `firefox_media_tests/urls/default.ini` are used for the tests, but you can also supply your own ini file of urls:
+
+ ```sh
+ $ firefox-media-tests --binary $FF_PATH --urls some/other/path/my_urls.ini
+ ```
+
+### Running EME tests
+
+In order to run EME tests, you must use a Firefox profile that has a signed plugin-container.exe and voucher.bin. With Netflix, this will be created when you log in and save the credentials. You must also use a custom .ini file for urls to the provider's content and indicate which test to run, like above. Ex:
+
+ ```sh
+ $ firefox-media-tests --binary $FF_PATH some/path/tests.ini --profile custom_profile --urls some/path/provider-urls.ini
+ ```
+
+
+### Running tests in a way that provides information about a crash
+
+What if Firefox crashes during a test run? You want to know why! To report useful crash data, the test runner needs access to a "minidump_stackwalk" binary and a "symbols.zip" file.
+
+1. Download a `minidump_stackwalk` binary for your platform (save it whereever). Get it from http://hg.mozilla.org/build/tools/file/tip/breakpad/.
+2. Make `minidump_stackwalk` executable
+
+ ```sh
+ $ chmod +x path/to/minidump_stackwalk
+ ```
+
+3. Create an environment variable called `MINIDUMP_STACKWALK` that points to that local path
+
+ ```sh
+ $ export MINIDUMP_STACKWALK=path/to/minidump_stackwalk
+ ```
+
+4. Download the `crashreporter-symbols.zip` file for the Firefox build you are testing and extract it. Example: ftp://ftp.mozilla.org/pub/firefox/tinderbox-builds/mozilla-aurora-win32/1427442016/firefox-38.0a2.en-US.win32.crashreporter-symbols.zip
+
+5. Run the tests with a `--symbols-path` flag
+
+ ```sh
+ $ firefox-media-tests --binary $FF_PATH --symbols-path path/to/example/firefox-38.0a2.en-US.win32.crashreporter-symbols
+ ```
+
+To check whether the above setup is working for you, trigger a (silly) Firefox crash while the tests are running. One way to do this is with the [crashme add-on](https://github.com/luser/crashme) -- you can add it to Firefox even while the tests are running. Another way on Linux and Mac OS systems:
+
+1. Find the process id (PID) of the Firefox process being used by the tests.
+
+ ```sh
+ $ ps x | grep 'Firefox'
+ ```
+
+2. Kill the Firefox process with SIGABRT.
+ ```sh
+ # 1234 is an example of a PID
+ $ kill -6 1234
+ ```
+
+Somewhere in the output produced by `firefox-media-tests`, you should see something like:
+
+```
+0:12.68 CRASH: MainThread pid:1234. Test:test_basic_playback.py TestVideoPlayback.test_playback_starts.
+Minidump anaylsed:False.
+Signature:[@ XUL + 0x2a65900]
+Crash dump filename:
+/var/folders/5k/xmn_fndx0qs2jcpcwhzl86wm0000gn/T/tmpB4Bolj.mozrunner/minidumps/DA3BB025-8302-4F96-8DF3-A97E424C877A.dmp
+Operating system: Mac OS X
+ 10.10.2 14C1514
+CPU: amd64
+ family 6 model 69 stepping 1
+ 4 CPUs
+
+Crash reason: EXC_SOFTWARE / SIGABRT
+Crash address: 0x104616900
+...
+```
+
+### Setting up for network shaping tests (browsermobproxy)
+
+1. Download the browsermob proxy zip file from http://bmp.lightbody.net/. The most current version as of this writing is browsermob-proxy-2.1.0-beta-2-bin.zip.
+2. Unpack the .zip file.
+3. Verify that you can launch browsermobproxy on your machine by running \<browsermob\>/bin/browsermob-proxy on your machine. I had to do a lot of work to install and use a java that browsermobproxy would like.
+4. Import the certificate into your Firefox profile. Select Preferences->Advanced->Certificates->View Certificates->Import... Navigate to <browsermob>/ssl-support and select cybervilliansCA.cer. Select all of the checkboxes.
+5. Tell marionette where browsermobproxy is and what port to start it on. Add the following command-line parameters to your firefox-media-tests command line:
+
+<pre><code>
+--browsermob-script <browsermob>/bin/browsermob-proxy --browsermob-port 999 --profile <your saved profile>
+</code></pre>
+
+On Windows, use browsermob-proxy.bat.
+
+You can then call browsermob to shape the network. You can find an example in firefox_media_tests/playback/test_playback_limiting_bandwidth.py. Another example can be found at https://dxr.mozilla.org/mozilla-central/source/testing/marionette/client/marionette/tests/unit/test_browsermobproxy.py.
+
+### A warning about video URLs
+The ini files in `firefox_media_tests/urls` may contain URLs pulled from Firefox crash or bug data. Automated tests don't care about video content, but you might: visit these at your own risk and be aware that they may be NSFW. We do not intend to ever moderate or filter these URLs.
+
+Writing a test
+--------------
+Write your test in a new or existing `test_*.py` file under `$PROJECT_HOME/firefox_media_tests`. Add it to the appropriate `manifest.ini` file(s) as well. Look in `media_utils` for useful video-playback functions.
+
+* [Marionette docs][marionette-docs]
+ - [Marionette Command Line Options](https://developer.mozilla.org/en-US/docs/Mozilla/Command_Line_Options)
+* [Firefox Puppeteer docs][ff-puppeteer-docs]
+
+License
+-------
+This software is licensed under the [Mozilla Public License v. 2.0](http://mozilla.org/MPL/2.0/).
+
+[marionette-python-tests]: https://developer.mozilla.org/en-US/docs/Mozilla/QA/Marionette/Marionette_Python_Tests
+[ff-puppeteer-docs]: http://firefox-puppeteer.readthedocs.org/en/latest/
+[marionette-docs]: http://marionette-client.readthedocs.org/en/latest/reference.html
+[ff-nightly]:https://nightly.mozilla.org/
new file mode 100644
--- /dev/null
+++ b/dom/media/test/external-media-tests/harness/__init__.py
@@ -0,0 +1,5 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from runtests import cli
new file mode 100644
--- /dev/null
+++ b/dom/media/test/external-media-tests/harness/runtests.py
@@ -0,0 +1,108 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from manifestparser import read_ini
+import os
+import sys
+
+from marionette import BaseMarionetteTestRunner, BaseMarionetteArguments
+from marionette.runner import BrowserMobProxyArguments
+from marionette.runtests import MarionetteHarness, cli as mn_cli
+import mozlog
+
+import media_tests
+from testcase import MediaTestCase
+from media_utils.video_puppeteer import debug_script
+
+
+class MediaTestArgumentsBase(object):
+ name = 'Firefox Media Tests'
+ args = [
+ [['--urls'], {
+ 'help': 'ini file of urls to make available to all tests',
+ 'default': os.path.join(media_tests.urls, 'default.ini'),
+ }],
+ ]
+
+ def verify_usage_handler(self, args):
+ if args.urls:
+ if not os.path.isfile(args.urls):
+ raise ValueError('--urls must provide a path to an ini file')
+ else:
+ path = os.path.abspath(args.urls)
+ args.video_urls = MediaTestArgumentsBase.get_urls(path)
+
+ def parse_args_handler(self, args):
+ if not args.tests:
+ args.tests = [media_tests.manifest]
+
+
+ @staticmethod
+ def get_urls(manifest):
+ with open(manifest, 'r'):
+ return [line[0] for line in read_ini(manifest)]
+
+
+class MediaTestArguments(BaseMarionetteArguments):
+ def __init__(self, **kwargs):
+ BaseMarionetteArguments.__init__(self, **kwargs)
+ self.register_argument_container(MediaTestArgumentsBase())
+ self.register_argument_container(BrowserMobProxyArguments())
+
+
+class MediaTestRunner(BaseMarionetteTestRunner):
+ def __init__(self, **kwargs):
+ BaseMarionetteTestRunner.__init__(self, **kwargs)
+ if not self.server_root:
+ self.server_root = media_tests.resources
+ # pick up prefs from marionette_driver.geckoinstance.DesktopInstance
+ self.app = 'fxdesktop'
+ self.test_handlers = [MediaTestCase]
+
+ # Used in HTML report (--log-html)
+ def gather_media_debug(test, status):
+ rv = {}
+ marionette = test._marionette_weakref()
+
+ if marionette.session is not None:
+ try:
+ with marionette.using_context(marionette.CONTEXT_CHROME):
+ debug_lines = marionette.execute_script(debug_script)
+ if debug_lines:
+ name = 'mozMediaSourceObject.mozDebugReaderData'
+ rv[name] = '\n'.join(debug_lines)
+ else:
+ logger = mozlog.get_default_logger()
+ logger.info('No data available about '
+ 'mozMediaSourceObject')
+ except:
+ logger = mozlog.get_default_logger()
+ logger.warning('Failed to gather test failure media debug',
+ exc_info=True)
+ return rv
+
+ self.result_callbacks.append(gather_media_debug)
+
+
+class FirefoxMediaHarness(MarionetteHarness):
+ def __init__(self,
+ runner_class=MediaTestRunner,
+ parser_class=MediaTestArguments):
+ # workaround until next marionette-client release - Bug 1227918
+ try:
+ MarionetteHarness.__init__(self, runner_class, parser_class)
+ except Exception:
+ logger = mozlog.commandline.setup_logging('Media-test harness', {})
+ logger.error('Failure setting up harness', exc_info=True)
+ raise
+
+ def parse_args(self, *args, **kwargs):
+ return MarionetteHarness.parse_args(self, {'mach': sys.stdout})
+
+
+def cli():
+ mn_cli(MediaTestRunner, MediaTestArguments, FirefoxMediaHarness)
+
+if __name__ == '__main__':
+ cli()
new file mode 100644
--- /dev/null
+++ b/dom/media/test/external-media-tests/harness/testcase.py
@@ -0,0 +1,138 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import os
+
+from marionette import BrowserMobProxyTestCaseMixin
+from marionette_driver import Wait
+from marionette_driver.errors import TimeoutException
+from marionette.marionette_test import SkipTest
+
+from firefox_puppeteer.testcases import FirefoxTestCase
+from media_tests.utils import (timestamp_now, verbose_until)
+from media_utils.video_puppeteer import (playback_done, playback_started,
+ VideoException, VideoPuppeteer as VP)
+
+
+class MediaTestCase(FirefoxTestCase):
+
+ def __init__(self, *args, **kwargs):
+ self.video_urls = kwargs.pop('video_urls', False)
+ FirefoxTestCase.__init__(self, *args, **kwargs)
+
+ def save_screenshot(self):
+ screenshot_dir = os.path.join(self.marionette.instance.workspace or '',
+ 'screenshots')
+ filename = ''.join([self.id().replace(' ', '-'),
+ '_',
+ str(timestamp_now()),
+ '.png'])
+ path = os.path.join(screenshot_dir, filename)
+ if not os.path.exists(screenshot_dir):
+ os.makedirs(screenshot_dir)
+ with self.marionette.using_context('content'):
+ img_data = self.marionette.screenshot()
+ with open(path, 'wb') as f:
+ f.write(img_data.decode('base64'))
+ self.marionette.log('Screenshot saved in %s' % os.path.abspath(path))
+
+ def log_video_debug_lines(self):
+ with self.marionette.using_context('chrome'):
+ debug_lines = self.marionette.execute_script(VP._debug_script)
+ if debug_lines:
+ self.marionette.log('\n'.join(debug_lines))
+
+ def run_playback(self, video):
+ with self.marionette.using_context('content'):
+ self.logger.info(video.test_url)
+ try:
+ verbose_until(Wait(video, interval=video.interval,
+ timeout=video.expected_duration * 1.3 +
+ video.stall_wait_time),
+ video, playback_done)
+ except VideoException as e:
+ raise self.failureException(e)
+
+ def check_playback_starts(self, video):
+ with self.marionette.using_context('content'):
+ self.logger.info(video.test_url)
+ try:
+ verbose_until(Wait(video, timeout=video.timeout),
+ video, playback_started)
+ except TimeoutException as e:
+ raise self.failureException(e)
+
+ def skipTest(self, reason):
+ """
+ Skip this test.
+
+ Skip with marionette.marionette_test import SkipTest so that it
+ gets recognized a skip in marionette.marionette_test.CommonTestCase.run
+ """
+ raise SkipTest(reason)
+
+
+class NetworkBandwidthTestCase(MediaTestCase):
+
+ def __init__(self, *args, **kwargs):
+ MediaTestCase.__init__(self, *args, **kwargs)
+ BrowserMobProxyTestCaseMixin.__init__(self, *args, **kwargs)
+ self.proxy = None
+
+ def setUp(self):
+ MediaTestCase.setUp(self)
+ BrowserMobProxyTestCaseMixin.setUp(self)
+ self.proxy = self.create_browsermob_proxy()
+
+ def tearDown(self):
+ MediaTestCase.tearDown(self)
+ BrowserMobProxyTestCaseMixin.tearDown(self)
+ self.proxy = None
+
+
+ def run_videos(self):
+ with self.marionette.using_context('content'):
+ for url in self.video_urls:
+ video = VP(self.marionette, url,
+ stall_wait_time=60,
+ set_duration=60)
+ self.run_playback(video)
+
+
+class VideoPlaybackTestsMixin(object):
+
+ """ Test MSE playback in HTML5 video element.
+
+ These tests should pass on any site where a single video element plays
+ upon loading and is uninterrupted (by ads, for example).
+
+ This test both starting videos and performing partial playback at one
+ minute each, and is the test that should be run frequently in automation.
+ """
+
+ def test_playback_starts(self):
+ with self.marionette.using_context('content'):
+ for url in self.video_urls:
+ try:
+ video = VP(self.marionette, url, timeout=60)
+ # Second playback_started check in case video._start_time
+ # is not 0
+ self.check_playback_starts(video)
+ video.pause()
+ src = video.video_src
+ if not src.startswith('mediasource'):
+ self.marionette.log('video is not '
+ 'mediasource: %s' % src,
+ level='WARNING')
+ except TimeoutException as e:
+ raise self.failureException(e)
+
+ def test_video_playback_partial(self):
+ """ First 60 seconds of video play well. """
+ with self.marionette.using_context('content'):
+ for url in self.video_urls:
+ video = VP(self.marionette, url,
+ stall_wait_time=10,
+ set_duration=60)
+ self.run_playback(video)
new file mode 100644
--- /dev/null
+++ b/dom/media/test/external-media-tests/media_tests/__init__.py
@@ -0,0 +1,10 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import os
+
+root = os.path.abspath(os.path.dirname(__file__))
+manifest = os.path.join(root, 'manifest.ini')
+resources = os.path.join(root, 'resources')
+urls = os.path.join(root, 'urls')
new file mode 100644
--- /dev/null
+++ b/dom/media/test/external-media-tests/media_tests/manifest.ini
@@ -0,0 +1,1 @@
+[include:playback/manifest.ini]
new file mode 100644
--- /dev/null
+++ b/dom/media/test/external-media-tests/media_tests/playback/eme.ini
@@ -0,0 +1,1 @@
+[test_eme_playback.py]
new file mode 100644
--- /dev/null
+++ b/dom/media/test/external-media-tests/media_tests/playback/limiting_bandwidth.ini
@@ -0,0 +1,2 @@
+[test_playback_limiting_bandwidth.py]
+[test_ultra_low_bandwidth.py]
new file mode 100644
--- /dev/null
+++ b/dom/media/test/external-media-tests/media_tests/playback/manifest.ini
@@ -0,0 +1,1 @@
+[test_video_playback.py]
new file mode 100644
--- /dev/null
+++ b/dom/media/test/external-media-tests/media_tests/playback/netflix_limiting_bandwidth.ini
@@ -0,0 +1,1 @@
+[test_playback_limiting_bandwidth.py]
new file mode 100644
--- /dev/null
+++ b/dom/media/test/external-media-tests/media_tests/playback/test_eme_playback.py
@@ -0,0 +1,71 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import re
+
+from harness.testcase import MediaTestCase, VideoPlaybackTestsMixin
+
+
+class TestEMEPlayback(MediaTestCase, VideoPlaybackTestsMixin):
+
+ def setUp(self):
+ super(TestEMEPlayback, self).setUp()
+ self.set_eme_prefs()
+ assert(self.check_eme_prefs())
+
+ def set_eme_prefs(self):
+ with self.marionette.using_context('chrome'):
+
+ # https://bugzilla.mozilla.org/show_bug.cgi?id=1187471#c28
+ # 2015-09-28 cpearce says this is no longer necessary, but in case
+ # we are working with older firefoxes...
+ self.prefs.set_pref('media.gmp.trial-create.enabled', False)
+
+ def check_and_log_boolean_pref(self, pref_name, expected_value):
+ with self.marionette.using_context('chrome'):
+ pref_value = self.prefs.get_pref(pref_name)
+
+ if pref_value is None:
+ self.logger.info('Pref %s has no value.' % pref_name)
+ return False
+ else:
+ self.logger.info('Pref %s = %s' % (pref_name, pref_value))
+ if pref_value != expected_value:
+ self.logger.info('Pref %s has unexpected value.'
+ % pref_name)
+ return False
+
+ return True
+
+ def check_and_log_integer_pref(self, pref_name, minimum_value=0):
+ with self.marionette.using_context('chrome'):
+ pref_value = self.prefs.get_pref(pref_name)
+
+ if pref_value is None:
+ self.logger.info('Pref %s has no value.' % pref_name)
+ return False
+ else:
+ self.logger.info('Pref %s = %s' % (pref_name, pref_value))
+
+ match = re.search('^\d+$', pref_value)
+ if not match:
+ self.logger.info('Pref %s is not an integer' % pref_name)
+ return False
+
+ return pref_value >= minimum_value
+
+ def check_eme_prefs(self):
+ with self.marionette.using_context('chrome'):
+ prefs_ok = self.check_and_log_boolean_pref(
+ 'media.mediasource.enabled', True)
+ prefs_ok = self.check_and_log_boolean_pref(
+ 'media.eme.enabled', True) and prefs_ok
+ prefs_ok = self.check_and_log_boolean_pref(
+ 'media.mediasource.mp4.enabled', True) and prefs_ok
+ prefs_ok = self.check_and_log_boolean_pref(
+ 'media.gmp-eme-adobe.enabled', True) and prefs_ok
+ prefs_ok = self.check_and_log_integer_pref(
+ 'media.gmp-eme-adobe.version', 1) and prefs_ok
+
+ return prefs_ok
new file mode 100644
--- /dev/null
+++ b/dom/media/test/external-media-tests/media_tests/playback/test_full_playback.py
@@ -0,0 +1,24 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+
+from harness.testcase import MediaTestCase
+from media_utils.video_puppeteer import VideoPuppeteer
+
+
+class TestFullPlayback(MediaTestCase):
+ """ Test MSE playback in HTML5 video element.
+
+ These tests should pass on any site where a single video element plays
+ upon loading and is uninterrupted (by ads, for example). This will play
+ the full videos, so it could take a while depending on the videos playing.
+ It should be run much less frequently in automated systems.
+ """
+
+ def test_video_playback_full(self):
+ with self.marionette.using_context('content'):
+ for url in self.video_urls:
+ video = VideoPuppeteer(self.marionette, url,
+ stall_wait_time=10)
+ self.run_playback(video)
new file mode 100644
--- /dev/null
+++ b/dom/media/test/external-media-tests/media_tests/playback/test_playback_limiting_bandwidth.py
@@ -0,0 +1,23 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from marionette import BrowserMobProxyTestCaseMixin
+
+from harness.testcase import NetworkBandwidthTestCase
+
+
+class TestPlaybackLimitingBandwidth(NetworkBandwidthTestCase,
+ BrowserMobProxyTestCaseMixin):
+
+ def test_playback_limiting_bandwidth_250(self):
+ self.proxy.limits({'downstream_kbps': 250})
+ self.run_videos()
+
+ def test_playback_limiting_bandwidth_500(self):
+ self.proxy.limits({'downstream_kbps': 500})
+ self.run_videos()
+
+ def test_playback_limiting_bandwidth_1000(self):
+ self.proxy.limits({'downstream_kbps': 1000})
+ self.run_videos()
new file mode 100644
--- /dev/null
+++ b/dom/media/test/external-media-tests/media_tests/playback/test_ultra_low_bandwidth.py
@@ -0,0 +1,15 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from marionette import BrowserMobProxyTestCaseMixin
+
+from harness.testcase import NetworkBandwidthTestCase
+
+
+class TestUltraLowBandwidth(NetworkBandwidthTestCase,
+ BrowserMobProxyTestCaseMixin):
+
+ def test_playback_limiting_bandwidth_160(self):
+ self.proxy.limits({'downstream_kbps': 160})
+ self.run_videos()
new file mode 100644
--- /dev/null
+++ b/dom/media/test/external-media-tests/media_tests/playback/test_video_playback.py
@@ -0,0 +1,15 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from harness.testcase import (
+ MediaTestCase,
+ VideoPlaybackTestsMixin
+)
+
+
+class TestVideoPlayback(MediaTestCase, VideoPlaybackTestsMixin):
+
+ # Tests are actually implemented in VideoPlaybackTestsMixin.
+
+ pass
new file mode 100644
--- /dev/null
+++ b/dom/media/test/external-media-tests/media_tests/playback/youtube/manifest.ini
@@ -0,0 +1,1 @@
+[test_basic_playback.py ]
new file mode 100644
--- /dev/null
+++ b/dom/media/test/external-media-tests/media_tests/playback/youtube/test_basic_playback.py
@@ -0,0 +1,73 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from marionette_driver import Wait
+from marionette_driver.errors import TimeoutException
+
+from media_tests.utils import verbose_until
+from harness.testcase import MediaTestCase
+from media_utils.video_puppeteer import VideoException
+from media_utils.youtube_puppeteer import (YouTubePuppeteer, playback_done,
+ wait_for_almost_done)
+
+
+class TestBasicYouTubePlayback(MediaTestCase):
+ def test_mse_is_enabled_by_default(self):
+ with self.marionette.using_context('content'):
+ youtube = YouTubePuppeteer(self.marionette, self.video_urls[0],
+ timeout=60)
+ wait = Wait(youtube,
+ timeout=min(300, youtube.expected_duration * 1.3),
+ interval=1)
+ try:
+ verbose_until(wait, youtube,
+ lambda y: y.video_src.startswith('mediasource'),
+ "Failed to find 'mediasource' in video src url.")
+ except TimeoutException as e:
+ raise self.failureException(e)
+
+ def test_video_playing_in_one_tab(self):
+ with self.marionette.using_context('content'):
+ for url in self.video_urls:
+ self.logger.info(url)
+ youtube = YouTubePuppeteer(self.marionette, url)
+ self.logger.info('Expected duration: %s' %
+ youtube.expected_duration)
+ youtube.deactivate_autoplay()
+
+ final_piece = 60
+ try:
+ time_left = wait_for_almost_done(youtube,
+ final_piece=final_piece)
+ except VideoException as e:
+ raise self.failureException(e)
+ duration = abs(youtube.expected_duration) + 1
+ if duration > 1:
+ self.logger.info('Almost done: %s - %s seconds left.' %
+ (youtube.movie_id, time_left))
+ if time_left > final_piece:
+ self.marionette.log('time_left greater than '
+ 'final_piece - %s' % time_left,
+ level='WARNING')
+ self.save_screenshot()
+ else:
+ self.marionette.log('Duration close to 0 - %s' % youtube,
+ level='WARNING')
+ self.save_screenshot()
+ try:
+ verbose_until(Wait(youtube,
+ timeout=max(100, time_left) * 1.3,
+ interval=1),
+ youtube,
+ playback_done)
+ except TimeoutException as e:
+ raise self.failureException(e)
+
+ def test_playback_starts(self):
+ with self.marionette.using_context('content'):
+ for url in self.video_urls:
+ try:
+ YouTubePuppeteer(self.marionette, url, timeout=60)
+ except TimeoutException as e:
+ raise self.failureException(e)
new file mode 100644
--- /dev/null
+++ b/dom/media/test/external-media-tests/media_tests/playback/youtube/test_prefs.py
@@ -0,0 +1,49 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from harness.testcase import MediaTestCase
+from marionette_driver import Wait
+
+from media_tests.utils import verbose_until
+from media_utils.youtube_puppeteer import YouTubePuppeteer
+
+
+class TestMediaSourcePrefs(MediaTestCase):
+ def setUp(self):
+ MediaTestCase.setUp(self)
+ self.test_urls = self.video_urls[:2]
+ self.max_timeout = 60
+
+ def tearDown(self):
+ MediaTestCase.tearDown(self)
+
+ def test_mse_prefs(self):
+ """ 'mediasource' should only be used if MSE prefs are enabled."""
+ self.set_mse_enabled_prefs(False)
+ self.check_src('http', self.test_urls[0])
+
+ self.set_mse_enabled_prefs(True)
+ self.check_src('mediasource', self.test_urls[0])
+
+ def set_mse_enabled_prefs(self, value):
+ with self.marionette.using_context('chrome'):
+ self.prefs.set_pref('media.mediasource.enabled', value)
+ self.prefs.set_pref('media.mediasource.mp4.enabled', value)
+
+ def check_src(self, src_type, url):
+ # Why wait to check src until initial ad is done playing?
+ # - src attribute in video element is sometimes null during ad playback
+ # - many ads still don't use MSE even if main video does
+ with self.marionette.using_context('content'):
+ youtube = YouTubePuppeteer(self.marionette, url)
+ youtube.attempt_ad_skip()
+ wait = Wait(youtube,
+ timeout=min(self.max_timeout,
+ youtube.player_duration * 1.3),
+ interval=1)
+
+ def cond(y):
+ return y.video_src.startswith(src_type)
+
+ verbose_until(wait, youtube, cond)
new file mode 100644
--- /dev/null
+++ b/dom/media/test/external-media-tests/media_tests/resources/mozilla.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html lang="en" dir="ltr">
+<head>
+ <title>Mozilla</title>
+ <link rel="shortcut icon" type="image/ico" href="../images/mozilla_favicon.ico" />
+</head>
+
+<body>
+ <a href="mozilla.html">
+ <img id="mozilla_logo" src="../images/mozilla_logo.jpg" />
+ </a>
+
+ <a href="#community">RARARARARARA</a> |
+ <a href="#project">Project</a> |
+ <a href="#organization">Organization</a>
+
+ <div id="content">
+ <h1 id="page-title">
+ <strong>RARARARARARA</strong> that the internet should be public,
+ open and accessible.
+ </h1>
+
+ <h2><a name="community">RARARARARARA</a></h2>
+ <p id="community">
+ We're a global community of thousands who believe in the power
+ of technology to enrich people's lives.
+ <a href="mozilla_community.html">More</a>
+ </p>
+
+ <h2><a name="project">Project</a></h2>
+ <p id="project">
+ We're an open source project whose code is used for some of the
+ Internet's most innovative applications.
+ <a href="mozilla_projects.html">More</a>
+ </p>
+
+ <h2><a name="organization">Organization</a></h2>
+ <p id="organization">
+ We're a public benefit organization dedicated to making the
+ Internet better for everyone.
+ <a href="mozilla_mission.html">More</a>
+ </p>
+ </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/media/test/external-media-tests/media_tests/test_example.py
@@ -0,0 +1,19 @@
+from harness.testcase import MediaTestCase
+
+
+class TestSomethingElse(MediaTestCase):
+ def setUp(self):
+ MediaTestCase.setUp(self)
+ self.test_urls = [
+ 'mozilla.html',
+ ]
+ self.test_urls = [self.marionette.absolute_url(t)
+ for t in self.test_urls]
+
+ def tearDown(self):
+ MediaTestCase.tearDown(self)
+
+ def test_foo(self):
+ self.logger.info('foo!')
+ with self.marionette.using_context('content'):
+ self.marionette.navigate(self.test_urls[0])
new file mode 100644
--- /dev/null
+++ b/dom/media/test/external-media-tests/media_tests/urls/default.ini
@@ -0,0 +1,9 @@
+# short videos; no ads; max 5 minutes
+# 0:12
+[https://youtu.be/AbAACm1IQE0]
+# 2:18
+[https://www.youtube.com/watch?v=yOQQCoxs8-k]
+# 0:08
+[https://www.youtube.com/watch?v=1visYpIREUM]
+# 2:09
+[https://www.youtube.com/watch?v=rjmuKV9BTkE]
new file mode 100644
--- /dev/null
+++ b/dom/media/test/external-media-tests/media_tests/urls/netflix/default.ini
@@ -0,0 +1,8 @@
+# YouTube test
+#[https://www.youtube.com/watch?v=AbAACm1IQE0]
+# ClearKey - 11:07
+[http://www.netflix.com/watch/70136810]
+# NoDRM - 2:24:xx
+[http://www.netflix.com/watch/70304192]
+# DRM - 24:47
+[http://www.netflix.com/watch/80015538]
new file mode 100644
--- /dev/null
+++ b/dom/media/test/external-media-tests/media_tests/urls/youtube/archive/crash_videos.ini
@@ -0,0 +1,25 @@
+[https://www.youtube.com/watch?v=2GfaRuIMdos]
+[https://www.youtube.com/watch?v=9vKvcCNt40g]
+[https://www.youtube.com/watch?v=SHLLHya2pNo]
+[https://www.youtube.com/watch?v=isMEMDE2enU]
+[https://www.youtube.com/watch?v=H81M_MebLsk]
+[https://www.youtube.com/watch?v=yopNkcDzQQw]
+[https://www.youtube.com/watch?v=r_bG5beSqw0]
+[https://www.youtube.com/watch?v=Ki9sSZKClO0]
+[https://www.youtube.com/watch?v=gNS04P8djk4]
+[https://www.youtube.com/watch?v=DwC_6fIBW0w]
+[https://www.youtube.com/watch?v=g1D3A14o0NA]
+[https://www.youtube.com/watch?v=cs-XZ_dN4Hc]
+[https://www.youtube.com/watch?v=ZEWZ3AAH98c]
+[https://www.youtube.com/watch?v=hwbVGE4GBJI]
+[https://www.youtube.com/watch?v=cvcMnbkasIs]
+[https://www.youtube.com/watch?v=cHaBuoHwQ0Y]
+[https://www.youtube.com/watch?v=VKIYoAG9MZ0]
+[https://www.youtube.com/watch?v=WWDb2_unEJc]
+[https://www.youtube.com/watch?v=ybw5zonQffE]
+[https://www.youtube.com/watch?v=hS6ps2Xph_o]
+[https://www.youtube.com/watch?v=Bjb3xhgIqv4]
+[https://www.youtube.com/watch?v=fOzvEhX4Kvk]
+[https://www.youtube.com/watch?v=_TNsUxp_BxM]
+[https://www.youtube.com/watch?v=QRdwCSHF3oo]
+[https://www.youtube.com/watch?v=VwaHFcKJSYA]
new file mode 100644
--- /dev/null
+++ b/dom/media/test/external-media-tests/media_tests/urls/youtube/archive/other_videos.ini
@@ -0,0 +1,19 @@
+# backlog of videos
+[http://youtu.be/2iVAvSnofy8]
+
+# 300s <= duration <= 1200s (5-20min)
+[http://youtu.be/9bZkp7q19f0]
+[http://youtu.be/KQ6zr6kCPj8]
+
+# duration > 1200s (>20min)
+[http://youtu.be/wZZ7oFKsKzY]
+[http://youtu.be/eHUrC_UiZwY]
+[http://youtu.be/FLX64H5FYa8]
+[http://youtu.be/Fu2DcHzokew]
+
+#no_ad_tests_youtube
+#[http://youtu.be/pWI8RB2dmfU]
+#[http://youtu.be/6GBtEmtVObw]
+
+#playlist_tests_youtube
+#[http://youtu.be/R6KJjPqlPz4?list=PL75_HhpYGJQ1Fzv9a46FlHfiy-fJusKBZ]
new file mode 100644
--- /dev/null
+++ b/dom/media/test/external-media-tests/media_tests/urls/youtube/archive/video_data.ini
@@ -0,0 +1,21 @@
+# duration < 300s (5min)
+[http://youtu.be/065dlrJoHcw]
+[http://youtu.be/1visYpIREUM]
+[http://youtu.be/mDf7CR5QKcE]
+[http://youtu.be/Aebs62bX0dA]
+[http://youtu.be/6SFp1z7uA6g]
+[http://youtu.be/tDDVAErOI5U]
+
+# ad testing
+[https://www.youtube.com/watch?v=l5ODwR6FPRQ]
+[https://www.youtube.com/watch?v=7RMQksXpQSk]
+
+# duration > 5 min
+# video with ad in the middle
+[https://www.youtube.com/watch?v=cht9Xq9suGg]
+
+# long video (>30 min), no ads
+[https://www.youtube.com/watch?v=-qXxNPvqHtQ]
+
+# bug 1144172, duration ~ 1hr
+#[https://www.youtube.com/watch?v=AYYDshv8C4g]
new file mode 100644
--- /dev/null
+++ b/dom/media/test/external-media-tests/media_tests/urls/youtube/archive/youtube.ini
@@ -0,0 +1,38 @@
+# < 1 no ads
+[https://youtu.be/AbAACm1IQE0]
+[https://www.youtube.com/watch?v=KdHZwWQWNyM]
+[https://www.youtube.com/watch?v=-hVmkA_I9EE]
+[https://www.youtube.com/watch?v=1visYpIREUM]
+
+# 1 < t <= 5 no ads
+[https://www.youtube.com/watch?v=rpYRAs6ePY8]
+[https://www.youtube.com/watch?v=xcgUKzwg0Mo]
+[https://youtu.be/sEAT2EFIJow]
+[https://www.youtube.com/watch?v=SSgnbQ5UC48]
+[https://youtu.be/4oQu26IhiaA]
+[https://youtu.be/IbND63HOb0M]
+[https://youtu.be/-9sJp9wrdAk]
+[https://www.youtube.com/watch?v=yIQGH4aQWI0]
+
+# 1 < t <= 5
+[https://www.youtube.com/watch?v=-hVmkA_I9EE]
+[https://www.youtube.com/watch?v=l5ODwR6FPRQ]
+[https://www.youtube.com/watch?v=7RMQksXpQSk]
+[https://www.youtube.com/watch?v=TsXMe8H6iyc]
+[https://www.youtube.com/watch?v=tDDVAErOI5U]
+
+# 5 < t <= 10
+[https://youtu.be/Tl-hI2IsCo0] # no ad
+[https://www.youtube.com/watch?v=IX_d_vMKswE] #no ad
+[https://www.youtube.com/watch?v=YVQeTY-Ayko] #no ad
+[https://www.youtube.com/watch?v=rE3j_RHkqJc]
+[https://www.youtube.com/watch?v=l4bmZ1gRqCc]
+
+# 10 < t <= 30
+[https://www.youtube.com/watch?v=RvymAHt3nPc] # no ads
+[https://www.youtube.com/watch?v=8XQ1onjXJK0]
+[https://www.youtube.com/watch?v=6Lm9EHhbJAY]
+[https://www.youtube.com/watch?v=cht9Xq9suGg]
+
+# long video (>30 min), no ads
+[https://www.youtube.com/watch?v=-qXxNPvqHtQ]
new file mode 100644
--- /dev/null
+++ b/dom/media/test/external-media-tests/media_tests/urls/youtube/long1-720.ini
@@ -0,0 +1,14 @@
+# all long videos; < 12 hours total
+# 2:18:00
+[http://youtu.be/FLX64H5FYa8]
+# 1:00:00
+[https://www.youtube.com/watch?v=AYYDshv8C4g]
+# 1:10:00
+[https://www.youtube.com/watch?v=V0Vy4kYAPDk]
+# 1:47:00
+[https://www.youtube.com/watch?v=bFtGE2C7Pxs]
+
+
+# shutdownhang | WaitForSingleObjectEx | WaitForSingleObject | PR_Wait | nsThread::ProcessNextEvent(bool, bool*) | NS_ProcessNextEvent(nsIThread*, bool) | mozilla::MediaShutdownManager::Shutdown()
+# 1:43:00
+[https://www.youtube.com/watch?v=BXMtXpmpXPU]
new file mode 100644
--- /dev/null
+++ b/dom/media/test/external-media-tests/media_tests/urls/youtube/long2-720.ini
@@ -0,0 +1,9 @@
+# a couple of very long videos, < 12 hours total
+# 6:00:00
+[https://www.youtube.com/watch?v=5N8sUccRiTA]
+# 2:27:00
+[https://www.youtube.com/watch?v=NAVrm3wjzq8]
+# 58:50
+[https://www.youtube.com/watch?v=uP1BBw3IYco]
+# 2:09:00
+[https://www.youtube.com/watch?v=b6q5N16dje4]
new file mode 100644
--- /dev/null
+++ b/dom/media/test/external-media-tests/media_tests/urls/youtube/long3-crashes-720.ini
@@ -0,0 +1,36 @@
+# videos from crashes, < 12 hours
+
+# hang | NtUserMessageCall | SendMessageW
+# 1:10:00
+[https://www.youtube.com/watch?v=Ztie4DqeOak]
+
+# nsPluginInstanceOwner::GetDocument(nsIDocument**)
+# 22:40
+[https://www.youtube.com/watch?v=D4cLM_JRrAU]
+# 16:47
+[https://www.youtube.com/watch?v=3C2r05Lxsrk]
+
+# F1398665248_____________________________
+# 1:06:00
+[https://www.youtube.com/watch?v=59gTMBss8o0]
+# 50:58
+[https://www.youtube.com/watch?v=_7VFIZhR744]
+# 44:54
+[https://www.youtube.com/watch?v=d6ro4Oq5msA]
+
+# hang | WaitForMultipleObjectsEx | RealMsgWaitForMultipleObjectsEx | MsgWaitForMultipleObjects | F_1152915508___________________________________
+#1:07:12
+[https://www.youtube.com/watch?v=Ffkf3tosmKw]
+# 1:02:00
+[https://www.youtube.com/watch?v=dC3AHEao2MI]
+
+# hang | BaseGetNamedObjectDirectory | RealMsgWaitForMultipleObjectsEx | MsgWaitForMultipleObjects | F_1152915508___________________________________
+# 10:00
+[https://www.youtube.com/watch?v=fn3Qb56ujNQ]
+# 5:00
+[https://www.youtube.com/watch?v=gBsh1bT8ltI]
+# 03:50:12
+[https://www.youtube.com/watch?v=TdW4S8zbmJQ]
+
+
+
new file mode 100644
--- /dev/null
+++ b/dom/media/test/external-media-tests/media_tests/urls/youtube/long4-crashes-900.ini
@@ -0,0 +1,83 @@
+# Total time: about 12-13 hours + unskippable ads
+#Request url: https://crash-stats.mozilla.com/api/SuperSearchUnredacted/?product=Firefox&url=%24https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3D&url=%21~list&url=%21~index&_results_number=50&platform=Windows&version=37.0&date=%3E2015-03-26
+
+#Request url: https://crash-stats.mozilla.com/api/SuperSearchUnredacted/?product=Firefox&url=%24https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3D&url=%21~list&url=%21~index&_results_number=5&platform=Windows&version=37.0&signature=%3Dhang+%7C+NtUserMessageCall+%7C+SendMessageW&date=%3E2015-03-26
+
+#Request url: https://crash-stats.mozilla.com/api/SuperSearchUnredacted/?product=Firefox&url=%24https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3D&url=%21~list&url=%21~index&_results_number=5&platform=Windows&version=37.0&signature=%3DOOM+%7C+small&date=%3E2015-03-26
+
+#Request url: https://crash-stats.mozilla.com/api/SuperSearchUnredacted/?product=Firefox&url=%24https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3D&url=%21~list&url=%21~index&_results_number=5&platform=Windows&version=37.0&signature=%3Dmozilla%3A%3Alayers%3A%3ACompositorD3D11%3A%3AHandleError%28long%2C+mozilla%3A%3Alayers%3A%3ACompositorD3D11%3A%3ASeverity%29+%7C+mozilla%3A%3Alayers%3A%3ACompositorD3D11%3A%3AFailed%28long%2C+mozilla%3A%3Alayers%3A%3ACompositorD3D11%3A%3ASeverity%29+%7C+mozilla%3A%3Alayers%3A%3ACompositorD3D11%3A%3AUpdateRenderTarget%28%29&date=%3E2015-03-26
+
+#Request url: https://crash-stats.mozilla.com/api/SuperSearchUnredacted/?product=Firefox&url=%24https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3D&url=%21~list&url=%21~index&_results_number=5&platform=Windows&version=37.0&signature=%3DOOM+%7C+large+%7C+mozalloc_abort%28char+const%2A+const%29+%7C+mozalloc_handle_oom%28unsigned+int%29+%7C+moz_xmalloc+%7C+nsTArray_base%3CnsTArrayInfallibleAllocator%2C+nsTArray_CopyWithMemutils%3E%3A%3AEnsureCapacity%28unsigned+int%2C+unsigned+int%29+%7C+nsTArray_base%3CnsTArrayInfallibleAllo...&date=%3E2015-03-26
+
+#Request url: https://crash-stats.mozilla.com/api/SuperSearchUnredacted/?product=Firefox&url=%24https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3D&url=%21~list&url=%21~index&_results_number=5&platform=Windows&version=37.0&signature=%3Dshutdownhang+%7C+WaitForSingleObjectEx+%7C+WaitForSingleObject+%7C+PR_Wait+%7C+nsThread%3A%3AProcessNextEvent%28bool%2C+bool%2A%29+%7C+NS_ProcessNextEvent%28nsIThread%2A%2C+bool%29+%7C+mozilla%3A%3AMediaShutdownManager%3A%3AShutdown%28%29&date=%3E2015-03-26
+
+#Request url: https://crash-stats.mozilla.com/api/SuperSearchUnredacted/?product=Firefox&url=%24https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3D&url=%21~list&url=%21~index&_results_number=5&platform=Windows&version=37.0&signature=%3Dmozilla%3A%3Alayers%3A%3ACompositorD3D11%3A%3AUpdateConstantBuffers%28%29&date=%3E2015-03-26
+
+#Request url: https://crash-stats.mozilla.com/api/SuperSearchUnredacted/?product=Firefox&url=%24https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3D&url=%21~list&url=%21~index&_results_number=5&platform=Windows&version=37.0&signature=%3Dmsvcr120.dll%400xf20c&date=%3E2015-03-26
+
+#Request url: https://crash-stats.mozilla.com/api/SuperSearchUnredacted/?product=Firefox&url=%24https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3D&url=%21~list&url=%21~index&_results_number=5&platform=Windows&version=37.0&signature=%3Djs%3A%3AGCMarker%3A%3AprocessMarkStackTop%28js%3A%3ASliceBudget%26%29&date=%3E2015-03-26
+
+#Request url: https://crash-stats.mozilla.com/api/SuperSearchUnredacted/?product=Firefox&url=%24https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3D&url=%21~list&url=%21~index&_results_number=5&platform=Windows&version=37.0&signature=%3Dshutdownhang+%7C+WaitForSingleObjectEx+%7C+WaitForSingleObject+%7C+PR_Wait+%7C+nsThread%3A%3AProcessNextEvent%28bool%2C+bool%2A%29+%7C+NS_ProcessNextEvent%28nsIThread%2A%2C+bool%29+%7C+mozilla%3A%3Alayers%3A%3ACompositorParent%3A%3AShutDown%28%29&date=%3E2015-03-26
+
+#Request url: https://crash-stats.mozilla.com/api/SuperSearchUnredacted/?product=Firefox&url=%24https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3D&url=%21~list&url=%21~index&_results_number=5&platform=Windows&version=37.0&signature=%3Dshutdownhang+%7C+WaitForSingleObjectEx+%7C+WaitForSingleObject+%7C+PR_Wait+%7C+mozilla%3A%3AReentrantMonitor%3A%3AWait%28unsigned+int%29+%7C+mozilla%3A%3Alayers%3A%3AImageBridgeChild%3A%3AShutDown%28%29&date=%3E2015-03-26
+
+# shutdownhang | WaitForSingleObjectEx | WaitForSingleObject | PR_Wait | nsThread::ProcessNextEvent(bool, bool*) | NS_ProcessNextEvent(nsIThread*, bool) | mozilla::MediaShutdownManager::Shutdown()
+[https://www.youtube.com/watch?v=PnwS01Yu9bs]
+[https://www.youtube.com/watch?v=6hNOMhEqI9g]
+[https://www.youtube.com/watch?v=gK9eCjYEwH4]
+#[https://www.youtube.com/watch?v=E9DFupLEV7c] Geographic restriction
+[https://www.youtube.com/watch?v=sLEVm0OGImU]
+# hang | NtUserMessageCall | SendMessageW
+[https://www.youtube.com/watch?v=kt0g4dWxEBo]
+[https://www.youtube.com/watch?v=cvwMS6UmesQ]
+[https://www.youtube.com/watch?v=Bj3YSTu3jUs]
+[https://www.youtube.com/watch?v=J9bgaoXLbFI]
+[https://www.youtube.com/watch?v=d5GUd6IElIw]
+# shutdownhang | WaitForSingleObjectEx | WaitForSingleObject | PR_Wait | mozilla::ReentrantMonitor::Wait(unsigned int) | mozilla::layers::ImageBridgeChild::ShutDown()
+[https://www.youtube.com/watch?v=6FMNFvKEy4c]
+[https://www.youtube.com/watch?v=w4RNIyJw9RI]
+#[https://www.youtube.com/watch?v=tKB5S1yp5MA] Account terminated
+[https://www.youtube.com/watch?v=Tct2Iv1QRUU]
+[https://www.youtube.com/watch?v=zDHOW9PdQYE]
+# shutdownhang | WaitForSingleObjectEx | WaitForSingleObject | PR_Wait | nsThread::ProcessNextEvent(bool, bool*) | NS_ProcessNextEvent(nsIThread*, bool) | mozilla::layers::CompositorParent::ShutDown()
+[https://www.youtube.com/watch?v=AGo24nC3_HU]
+[https://www.youtube.com/watch?v=GsVaCnud57U]
+[https://www.youtube.com/watch?v=zFg55zva7ok]
+#[https://www.youtube.com/watch?v=5VSk7bwPPOM] Policy violation
+[https://www.youtube.com/watch?v=2OYa5kR5EQ4]
+# OOM | large | mozalloc_abort(char const* const) | mozalloc_handle_oom(unsigned int) | moz_xmalloc | nsTArray_base<nsTArrayInfallibleAllocator, nsTArray_CopyWithMemutils>::EnsureCapacity(unsigned int, unsigned int) | nsTArray_base<nsTArrayInfallibleAllo...
+#[https://www.youtube.com/watch?v=1g91CAubt1c] Policy violation
+[https://www.youtube.com/watch?v=HE_7UFHPfQ0]
+# [https://www.youtube.com/watch?v=vhrM1JXG8-k] Live stream, Flash only
+[https://www.youtube.com/watch?v=ERWFf0JS94E]
+#[https://www.youtube.com/watch?v=8tmiawwVreE] Age restriction
+# mozilla::layers::CompositorD3D11::UpdateConstantBuffers()
+[https://www.youtube.com/watch?v=7azYa518LvE]
+[https://www.youtube.com/watch?v=Zg5JvdXHUqg]
+[https://www.youtube.com/watch?v=Q_kcoEY2wNw]
+[https://www.youtube.com/watch?v=eNzUJa0WjfU]
+[https://www.youtube.com/watch?v=B5V12xYb7hE]
+# OOM | small
+[https://www.youtube.com/watch?v=TS9Z8dN4OPo]
+[https://www.youtube.com/watch?v=EpngdStzhmQ]
+[https://www.youtube.com/watch?v=dUiDCX3BnM0]
+[https://www.youtube.com/watch?v=Ii4Su6Z8pCw]
+[https://www.youtube.com/watch?v=vviBJS6WQno]
+# msvcr120.dll@0xf20c
+[https://www.youtube.com/watch?v=hRE2VO9oa_g]
+[https://www.youtube.com/watch?v=qLL8VanC3zI]
+[https://www.youtube.com/watch?v=YX2LIztg2EI]
+[https://www.youtube.com/watch?v=-7Eh28eatBo]
+[https://www.youtube.com/watch?v=a32AMX55sZM]
+# js::GCMarker::processMarkStackTop(js::SliceBudget&)
+[https://www.youtube.com/watch?v=f0L2RzygE5k]
+[https://www.youtube.com/watch?v=-1RGIDgwHgM]
+[https://www.youtube.com/watch?v=iL1CEn7SQfQ]
+[https://www.youtube.com/watch?v=450p7goxZqg]
+[https://www.youtube.com/watch?v=Eo8c2sZ2eOY]
+# mozilla::layers::CompositorD3D11::HandleError(long, mozilla::layers::CompositorD3D11::Severity) | mozilla::layers::CompositorD3D11::Failed(long, mozilla::layers::CompositorD3D11::Severity) | mozilla::layers::CompositorD3D11::UpdateRenderTarget()
+[https://www.youtube.com/watch?v=a79R7bPhVhw]
+[https://www.youtube.com/watch?v=JRNCgvZs5v4]
+[https://www.youtube.com/watch?v=q8y58dWKfY8]
+[https://www.youtube.com/watch?v=Ns9M6sUvqxs]
+[https://www.youtube.com/watch?v=Ii-PCeTgR-A]
new file mode 100644
--- /dev/null
+++ b/dom/media/test/external-media-tests/media_tests/urls/youtube/massive-6000.ini
@@ -0,0 +1,15 @@
+# very long test; 96-100 hours?
+# 00:3:26
+[https://www.youtube.com/watch?v=7RMQksXpQSk]
+# nyan cat 10 hours
+[http://youtu.be/9bZkp7q19f0]
+# 4:54:00
+[https://www.youtube.com/watch?v=jWlKjw3LBDk]
+# 3:00:01
+[https://www.youtube.com/watch?v=ub9JUDS_6i8]
+# 10 hours rick roll
+[https://www.youtube.com/watch?v=BROWqjuTM0g]
+# 24 hours
+[https://www.youtube.com/watch?v=FvHiLLkPhQE]
+# 2 hours
+[https://www.youtube.com/watch?v=VmOuW5zTt9w
new file mode 100644
--- /dev/null
+++ b/dom/media/test/external-media-tests/media_tests/urls/youtube/medium1-60.ini
@@ -0,0 +1,18 @@
+# mix of shorter/longer videos with/without ads, < 60 min
+# 4:59
+[http://youtu.be/pWI8RB2dmfU]
+# 0:46 ad at start
+[http://youtu.be/6SFp1z7uA6g]
+# 0:58 ad at start
+[http://youtu.be/Aebs62bX0dA]
+# 1:43 ad
+[https://www.youtube.com/watch?v=l5ODwR6FPRQ]
+# 8:00 ad
+[https://www.youtube.com/watch?v=KlyXNRrsk4A]
+# video with ad in beginning and in the middle 20:00
+# https://bugzilla.mozilla.org/show_bug.cgi?id=1176815
+[https://www.youtube.com/watch?v=cht9Xq9suGg]
+# 1:35 ad
+[https://www.youtube.com/watch?v=orybDrUj4vA]
+# 3:02 - ad
+[https://youtu.be/tDDVAErOI5U]
new file mode 100644
--- /dev/null
+++ b/dom/media/test/external-media-tests/media_tests/urls/youtube/medium2-60.ini
@@ -0,0 +1,6 @@
+# a few longer videos, < 60 min total
+# 0:30:00 no ad
+[https://www.youtube.com/watch?v=-qXxNPvqHtQ]
+# 0:20:00
+[http://youtu.be/Fu2DcHzokew]
+
new file mode 100644
--- /dev/null
+++ b/dom/media/test/external-media-tests/media_tests/urls/youtube/medium3-120.ini
@@ -0,0 +1,11 @@
+# a few longer videos, < 120 min total
+# video with ad in the middle
+# 21:00
+[https://www.youtube.com/watch?v=cht9Xq9suGg]
+# 16:00
+[https://www.youtube.com/watch?v=6Lm9EHhbJAY]
+# 20:00
+[https://www.youtube.com/watch?v=8XQ1onjXJK0]
+# 59:06
+[https://www.youtube.com/watch?v=kmpiY5kssU4]
+
new file mode 100644
--- /dev/null
+++ b/dom/media/test/external-media-tests/media_tests/urls/youtube/short0-10.ini
@@ -0,0 +1,13 @@
+# short videos; no ads; max 10 minutes
+# 0:12
+[https://youtu.be/AbAACm1IQE0]
+# 0:30
+[https://www.youtube.com/watch?v=KdHZwWQWNyM]
+# 0:08
+[https://www.youtube.com/watch?v=1visYpIREUM]
+# 3:27
+[https://www.youtube.com/watch?v=xcgUKzwg0Mo]
+# 1:21
+[https://youtu.be/sEAT2EFIJow]
+# 1:23
+[https://www.youtube.com/watch?v=SSgnbQ5UC48]
new file mode 100644
--- /dev/null
+++ b/dom/media/test/external-media-tests/media_tests/urls/youtube/short1-15.ini
@@ -0,0 +1,5 @@
+# 00:12
+[https://youtu.be/AbAACm1IQE0]
+# longer video with ads; < 15 min total
+# 13:40
+[https://www.youtube.com/watch?v=87uo2TPrsl8]
new file mode 100644
--- /dev/null
+++ b/dom/media/test/external-media-tests/media_tests/urls/youtube/short2-15.ini
@@ -0,0 +1,5 @@
+# 1-2 longer videos with ads; < 15 minutes total
+[https://www.youtube.com/watch?v=v678Em6qyzk]
+[https://www.youtube.com/watch?v=l8XOZJkozfI]
+
+
new file mode 100644
--- /dev/null
+++ b/dom/media/test/external-media-tests/media_tests/urls/youtube/short3-crashes-15.ini
@@ -0,0 +1,14 @@
+# crash-data videos, < 15 minutes total
+
+# hang | NtUserMessageCall | SendMessageW
+# 5:40
+[https://www.youtube.com/watch?v=UIobdRNLNek]
+
+# F1398665248_____________________________
+# 3:59
+[https://www.youtube.com/watch?v=XGotQYd-X6o]
+
+# hang | WaitForMultipleObjectsEx | RealMsgWaitForMultipleObjectsEx | MsgWaitForMultipleObjects | F_1152915508___________________________________
+# 4:07
+[https://www.youtube.com/watch?v=wQgppPHXJSs]
+
new file mode 100644
--- /dev/null
+++ b/dom/media/test/external-media-tests/media_tests/utils.py
@@ -0,0 +1,66 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import datetime
+import time
+import types
+
+from marionette_driver.errors import TimeoutException
+
+
+def timestamp_now():
+ return int(time.mktime(datetime.datetime.now().timetuple()))
+
+
+def verbose_until(wait, target, condition, message=""):
+ """
+ Performs a `wait`.until(condition)` and adds information about the state of
+ `target` to any resulting `TimeoutException`.
+
+ :param wait: a `marionette.Wait` instance
+ :param target: the object you want verbose output about if a
+ `TimeoutException` is raised
+ This is usually the input value provided to the `condition` used by
+ `wait`. Ideally, `target` should implement `__str__`
+ :param condition: callable function used by `wait.until()`
+ :param message: optional message to log when exception occurs
+
+ :return: the result of `wait.until(condition)`
+ """
+ if isinstance(condition, types.FunctionType):
+ name = condition.__name__
+ else:
+ name = str(condition)
+ err_message = '\n'.join([message,
+ 'condition: ' + name,
+ str(target)])
+
+ return wait.until(condition, message=err_message)
+
+
+
+def save_memory_report(marionette):
+ """
+ Saves memory report (like about:memory) to current working directory.
+ """
+ with marionette.using_context('chrome'):
+ marionette.execute_async_script("""
+ Components.utils.import("resource://gre/modules/Services.jsm");
+ let Cc = Components.classes;
+ let Ci = Components.interfaces;
+ let dumper = Cc["@mozilla.org/memory-info-dumper;1"].
+ getService(Ci.nsIMemoryInfoDumper);
+ // Examples of dirs: "CurProcD" usually 'browser' dir in
+ // current FF dir; "DfltDwnld" default download dir
+ let file = Services.dirsvc.get("CurProcD", Ci.nsIFile);
+ file.append("media-memory-report");
+ file.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0777);
+ file.append("media-memory-report.json.gz");
+ dumper.dumpMemoryReportsToNamedFile(file.path, null, null, false);
+ log('Saved memory report to ' + file.path);
+ // for dmd-enabled build
+ dumper.dumpMemoryInfoToTempDir("media", false, false);
+ marionetteScriptFinished(true);
+ return;
+ """, script_timeout=30000)
new file mode 100644
--- /dev/null
+++ b/dom/media/test/external-media-tests/media_utils/video_puppeteer.py
@@ -0,0 +1,254 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from time import clock, sleep
+
+from marionette_driver import By, expected, Wait
+
+from media_tests.utils import verbose_until
+
+
+# Adapted from
+# https://github.com/gavinsharp/aboutmedia/blob/master/chrome/content/aboutmedia.xhtml
+debug_script = """
+var mainWindow = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIWebNavigation)
+ .QueryInterface(Components.interfaces.nsIDocShellTreeItem)
+ .rootTreeItem
+ .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindow);
+var tabbrowser = mainWindow.gBrowser;
+for (var i=0; i < tabbrowser.browsers.length; ++i) {
+ var b = tabbrowser.getBrowserAtIndex(i);
+ var media = b.contentDocumentAsCPOW.getElementsByTagName('video');
+ for (var j=0; j < media.length; ++j) {
+ var ms = media[j].mozMediaSourceObject;
+ if (ms) {
+ debugLines = ms.mozDebugReaderData.split(\"\\n\");
+ return debugLines;
+ }
+ }
+}"""
+
+
+class VideoPuppeteer(object):
+ """
+ Wrapper to control and introspect HTML5 video elements.
+
+ A note about properties like current_time and duration:
+ These describe whatever stream is playing when the property is called.
+ It is possible that many different streams are dynamically spliced
+ together, so the video stream that is currently playing might be the main
+ video or it might be something else, like an ad, for example.
+
+ Inputs:
+ marionette - The marionette instance this runs in.
+ url - the URL of the page containing the video element.
+ video_selector - the selector of the element that we want to
+ watch. This is set by default to 'video', which is what most
+ sites use, but others should work.
+ interval - The polling interval that is used to check progress.
+ set_duration - When set to >0, the polling and checking will stop
+ at the number of seconds specified. Otherwise, this will stop
+ at the end of the video.
+ stall_wait_time - The amount of time to wait to see if a stall has
+ cleared. If 0, do not check for stalls.
+ timeout - The amount of time to wait until the video starts.
+ """
+ def __init__(self, marionette, url, video_selector='video', interval=1,
+ set_duration=0, stall_wait_time=0, timeout=60):
+ self.marionette = marionette
+ self.test_url = url
+ self.interval = interval
+ self.stall_wait_time = stall_wait_time
+ self.timeout = timeout
+ self._set_duration = set_duration
+ self.video = None
+ self.expected_duration = 0
+ self._start_time = 0
+ self._start_wall_time = 0
+ wait = Wait(self.marionette, timeout=self.timeout)
+ with self.marionette.using_context('content'):
+ self.marionette.navigate(self.test_url)
+ self.marionette.execute_script("""
+ log('URL: {0}');""".format(self.test_url))
+ verbose_until(wait, self,
+ expected.element_present(By.TAG_NAME, 'video'))
+ videos_found = self.marionette.find_elements(By.CSS_SELECTOR,
+ video_selector)
+ if len(videos_found) > 1:
+ self.marionette.log(type(self).__name__ + ': multiple video '
+ 'elements found. '
+ 'Using first.')
+ if len(videos_found) <= 0:
+ self.marionette.log(type(self).__name__ + ': no video '
+ 'elements found.')
+ return
+ self.video = videos_found[0]
+ self.marionette.execute_script("log('video element obtained');")
+ # To get an accurate expected_duration, playback must have started
+ wait = Wait(self, timeout=self.timeout)
+ verbose_until(wait, self, lambda v: v.current_time > 0,
+ "Check if video current_time > 0")
+ self._start_time = self.current_time
+ self._start_wall_time = clock()
+ self.update_expected_duration()
+
+ def update_expected_duration(self):
+ """
+ Update the duration of the target video at self.test_url (in seconds).
+
+ expected_duration represents the following: how long do we expect
+ playback to last before we consider the video to be 'complete'?
+ If we only want to play the first n seconds of the video,
+ expected_duration is set to n.
+ """
+ # self.duration is the duration of whatever is playing right now.
+ # In this case, we assume the video element always shows the same
+ # stream throughout playback (i.e. the are no ads spliced into the main
+ # video, for example), so self.duration is the duration of the main
+ # video.
+ video_duration = self.duration
+ set_duration = self._set_duration
+ # In case video starts at t > 0, adjust target time partial playback
+ if self._set_duration and self._start_time:
+ set_duration += self._start_time
+ if 0 < set_duration < video_duration:
+ self.expected_duration = set_duration
+ else:
+ self.expected_duration = video_duration
+
+ def get_debug_lines(self):
+ with self.marionette.using_context('chrome'):
+ debug_lines = self.marionette.execute_script(debug_script)
+ return debug_lines
+
+ def play(self):
+ self.execute_video_script('arguments[0].wrappedJSObject.play();')
+
+ def pause(self):
+ self.execute_video_script('arguments[0].wrappedJSObject.pause();')
+
+ @property
+ def duration(self):
+ """
+ Return duration in seconds of whatever stream is playing right now.
+ """
+ return self.execute_video_script('return arguments[0].'
+ 'wrappedJSObject.duration;') or 0
+
+ @property
+ def current_time(self):
+ """
+ Return current time of whatever stream is playing right now.
+ """
+ return self.execute_video_script(
+ 'return arguments[0].wrappedJSObject.currentTime;') or 0
+
+ @property
+ def remaining_time(self):
+ # Note that self.current_time could temporarily refer to a
+ # spliced-in ad
+ return self.expected_duration - self.current_time
+
+ @property
+ def video_src(self):
+ with self.marionette.using_context('content'):
+ return self.video.get_attribute('src')
+
+ @property
+ def total_frames(self):
+ return self.execute_video_script("""
+ return arguments[0].getVideoPlaybackQuality()["totalVideoFrames"];
+ """)
+
+ @property
+ def dropped_frames(self):
+ return self.execute_video_script("""return
+ arguments[0].getVideoPlaybackQuality()["droppedVideoFrames"];
+ """) or 0
+
+ @property
+ def corrupted_frames(self):
+ return self.execute_video_script("""return
+ arguments[0].getVideoPlaybackQuality()["corruptedVideoFrames"];
+ """) or 0
+
+ @property
+ def video_url(self):
+ return self.execute_video_script('return arguments[0].baseURI;')
+
+ @property
+ def lag(self):
+ # Note that self.current_time could temporarily refer to a
+ # spliced-in ad
+ elapsed_current_time = self.current_time - self._start_time
+ elapsed_wall_time = clock() - self._start_wall_time
+ return elapsed_wall_time - elapsed_current_time
+
+ def measure_progress(self):
+ initial = self.current_time
+ sleep(1)
+ return self.current_time - initial
+
+ def execute_video_script(self, script):
+ """ Execute JS script in 'content' context with access to video element.
+ :param script: script to be executed
+ `arguments[0]` in script refers to video element.
+ :return: value returned by script
+ """
+ with self.marionette.using_context('content'):
+ return self.marionette.execute_script(script,
+ script_args=[self.video])
+
+ def __str__(self):
+ messages = ['%s - test url: %s: {' % (type(self).__name__,
+ self.test_url)]
+ if self.video:
+ messages += [
+ '\t(video)',
+ '\tcurrent_time: {0},'.format(self.current_time),
+ '\tduration: {0},'.format(self.duration),
+ '\texpected_duration: {0},'.format(self.expected_duration),
+ '\tlag: {0},'.format(self.lag),
+ '\turl: {0}'.format(self.video_url),
+ '\tsrc: {0}'.format(self.video_src),
+ '\tframes total: {0}'.format(self.total_frames),
+ '\t - dropped: {0}'.format(self.dropped_frames),
+ '\t - corrupted: {0}'.format(self.corrupted_frames)
+ ]
+ else:
+ messages += ['\tvideo: None']
+ messages.append('}')
+ return '\n'.join(messages)
+
+
+class VideoException(Exception):
+ pass
+
+
+def playback_started(video):
+ try:
+ return video.current_time > video._start_time
+ except Exception as e:
+ print ('Got exception %s' % e)
+ return False
+
+
+def playback_done(video):
+ # If we are near the end and there is still a video element, then
+ # we are essentially done. If this happens to be last time we are polled
+ # before the video ends, we won't get another chance.
+ remaining_time = video.remaining_time
+ if abs(remaining_time) < video.interval:
+ return True
+
+ # Check to see if the video has stalled. Accumulate the amount of lag
+ # since the video started, and if it is too high, then raise.
+ if video.stall_wait_time and (video.lag > video.stall_wait_time):
+ raise VideoException('Video %s stalled.\n%s' % (video.video_url,
+ video))
+
+ # We are cruising, so we are not done.
+ return False
new file mode 100644
--- /dev/null
+++ b/dom/media/test/external-media-tests/media_utils/youtube_puppeteer.py
@@ -0,0 +1,451 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from time import sleep
+import re
+from json import loads
+
+from marionette_driver import By, expected, Wait
+from marionette_driver.errors import TimeoutException, NoSuchElementException
+from video_puppeteer import VideoPuppeteer, VideoException
+from media_tests.utils import verbose_until
+
+
+class YouTubePuppeteer(VideoPuppeteer):
+ """
+ Wrapper around a YouTube #movie_player element
+
+ Partial reference: https://developers.google.com/youtube/js_api_reference
+ """
+
+ _yt_player_state = {
+ 'UNSTARTED': -1,
+ 'ENDED': 0,
+ 'PLAYING': 1,
+ 'PAUSED': 2,
+ 'BUFFERING': 3,
+ 'CUED': 5
+ }
+ _yt_player_state_name = {v: k for k, v in _yt_player_state.items()}
+ _time_pattern = re.compile('(?P<minute>\d+):(?P<second>\d+)')
+
+ def __init__(self, marionette, url, **kwargs):
+ self.player = None
+ super(YouTubePuppeteer,
+ self).__init__(marionette, url,
+ video_selector='#movie_player video',
+ **kwargs)
+ wait = Wait(self.marionette, timeout=30)
+ with self.marionette.using_context('content'):
+ verbose_until(wait, self,
+ expected.element_present(By.ID, 'movie_player'))
+ self.player = self.marionette.find_element(By.ID, 'movie_player')
+ self.marionette.execute_script("log('#movie_player "
+ "element obtained');")
+ # When an ad is playing, self.player_duration indicates the duration
+ # of the spliced-in ad stream, not the duration of the main video, so
+ # we attempt to skip the ad first.
+ for attempt in range(5):
+ sleep(1)
+ self.process_ad()
+ if (self.ad_inactive and self.duration and not
+ self.player_buffering):
+ break
+ self.update_expected_duration()
+
+ def player_play(self):
+ """ Play via YouTube API. """
+ self.execute_yt_script('arguments[1].wrappedJSObject.playVideo();')
+
+ def player_pause(self):
+ """ Pause via YouTube API. """
+ self.execute_yt_script('arguments[1].wrappedJSObject.pauseVideo();')
+
+ @property
+ def player_duration(self):
+ """ Duration in seconds via YouTube API.
+ """
+ return self.execute_yt_script('return arguments[1].'
+ 'wrappedJSObject.getDuration();')
+
+ @property
+ def player_current_time(self):
+ """ Current time in seconds via YouTube API.
+ """
+ return self.execute_yt_script('return arguments[1].'
+ 'wrappedJSObject.getCurrentTime();')
+
+ @property
+ def player_remaining_time(self):
+ """ Remaining time in seconds via YouTube API.
+ """
+ return self.expected_duration - self.player_current_time
+
+ def player_measure_progress(self):
+ """ Playback progress in seconds via YouTube API.
+ """
+ initial = self.player_current_time
+ sleep(1)
+ return self.player_current_time - initial
+
+ def _get_player_debug_dict(self):
+ text = self.execute_yt_script('return arguments[1].'
+ 'wrappedJSObject.getDebugText();')
+ if text:
+ try:
+ return loads(text)
+ except ValueError:
+ self.marionette.log('Error loading json: DebugText',
+ level='DEBUG')
+
+ def execute_yt_script(self, script):
+ """ Execute JS script in 'content' context with access to video element and
+ YouTube #movie_player element.
+ :param script: script to be executed.
+ `arguments[0]` in script refers to video element, `arguments[1]` refers
+ to #movie_player element (YouTube).
+ :return: value returned by script
+ """
+ with self.marionette.using_context('content'):
+ return self.marionette.execute_script(script,
+ script_args=[self.video,
+ self.player])
+
+ @property
+ def playback_quality(self):
+ return self.execute_yt_script('return arguments[1].'
+ 'wrappedJSObject.getPlaybackQuality();')
+
+ @property
+ def movie_id(self):
+ return self.execute_yt_script('return arguments[1].'
+ 'wrappedJSObject.'
+ 'getVideoData()["video_id"];')
+
+ @property
+ def movie_title(self):
+ title = self.execute_yt_script('return arguments[1].'
+ 'wrappedJSObject.'
+ 'getVideoData()["title"];')
+ # title may include non-ascii characters; replace them to avoid
+ # UnicodeEncodeError in string formatting for log messages
+ return title.encode('ascii', 'replace')
+
+ @property
+ def player_url(self):
+ return self.execute_yt_script('return arguments[1].'
+ 'wrappedJSObject.getVideoUrl();')
+
+ @property
+ def player_state(self):
+ state = self.execute_yt_script('return arguments[1].'
+ 'wrappedJSObject.getPlayerState();')
+ return state
+
+ @property
+ def player_unstarted(self):
+ return self.player_state == self._yt_player_state['UNSTARTED']
+
+ @property
+ def player_ended(self):
+ return self.player_state == self._yt_player_state['ENDED']
+
+ @property
+ def player_playing(self):
+ return self.player_state == self._yt_player_state['PLAYING']
+
+ @property
+ def player_paused(self):
+ return self.player_state == self._yt_player_state['PAUSED']
+
+ @property
+ def player_buffering(self):
+ return self.player_state == self._yt_player_state['BUFFERING']
+
+ @property
+ def player_cued(self):
+ return self.player_state == self._yt_player_state['CUED']
+
+ @property
+ def ad_state(self):
+ # Note: ad_state is sometimes not accurate, due to some sort of lag?
+ return self.execute_yt_script('return arguments[1].'
+ 'wrappedJSObject.getAdState();')
+
+ @property
+ def ad_format(self):
+ """
+ When ad is not playing, ad_format is False.
+
+ :return: integer representing ad format, or False
+ """
+ state = self.get_ad_displaystate()
+ ad_format = False
+ if state:
+ with self.marionette.using_context('content'):
+ ad_format = self.marionette.execute_script("""
+ return arguments[0].adFormat;""",
+ script_args=[state])
+ return ad_format
+
+ @property
+ def ad_skippable(self):
+ state = self.get_ad_displaystate()
+ skippable = False
+ if state:
+ with self.marionette.using_context('content'):
+ skippable = self.marionette.execute_script("""
+ return arguments[0].skippable;""",
+ script_args=[state])
+ return skippable
+
+ def get_ad_displaystate(self):
+ # may return None
+ return self.execute_yt_script('return arguments[1].'
+ 'wrappedJSObject.'
+ 'getOption("ad", "displaystate");')
+
+ @property
+ def breaks_count(self):
+ """
+ Number of upcoming ad breaks.
+ """
+ breaks = self.execute_yt_script('return arguments[1].'
+ 'wrappedJSObject.'
+ 'getOption("ad", "breakscount")')
+ # if video is not associated with any ads, breaks will be null
+ return breaks or 0
+
+ @property
+ def ad_inactive(self):
+ return (self.ad_ended or
+ self.ad_state == self._yt_player_state['UNSTARTED'])
+
+ @property
+ def ad_playing(self):
+ return self.ad_state == self._yt_player_state['PLAYING']
+
+ @property
+ def ad_ended(self):
+ return self.ad_state == self._yt_player_state['ENDED']
+
+ def process_ad(self):
+ if self.attempt_ad_skip() or self.ad_inactive:
+ return
+ ad_timeout = (self.search_ad_duration() or 30) + 5
+ wait = Wait(self, timeout=ad_timeout, interval=1)
+ try:
+ self.marionette.log('process_ad: waiting %s s for ad' % ad_timeout)
+ verbose_until(wait, self, lambda y: y.ad_ended, "Check if ad ended")
+ except TimeoutException:
+ self.marionette.log('Waiting for ad to end timed out',
+ level='WARNING')
+
+ def attempt_ad_skip(self):
+ """
+ Attempt to skip ad by clicking on skip-add button.
+ Return True if clicking of ad-skip button occurred.
+ """
+ # Wait for ad to load and become skippable
+ if self.ad_playing:
+ self.marionette.log('Waiting while ad plays')
+ sleep(10)
+ else:
+ # no ad playing
+ return False
+ if self.ad_skippable:
+ selector = '#movie_player .videoAdUiSkipContainer'
+ wait = Wait(self.marionette, timeout=30)
+ try:
+ with self.marionette.using_context('content'):
+ wait.until(expected.element_displayed(By.CSS_SELECTOR,
+ selector))
+ ad_button = self.marionette.find_element(By.CSS_SELECTOR,
+ selector)
+ ad_button.click()
+ self.marionette.log('Skipped ad.')
+ return True
+ except (TimeoutException, NoSuchElementException):
+ self.marionette.log('Could not obtain '
+ 'element: %s' % selector,
+ level='WARNING')
+ return False
+
+ def search_ad_duration(self):
+ """
+ :return: ad duration in seconds, if currently displayed in player
+ """
+ if not (self.ad_playing or self.player_measure_progress() == 0):
+ return None
+ # If the ad is not Flash...
+ if (self.ad_playing and self.video_src.startswith('mediasource') and
+ self.duration):
+ return self.duration
+ selector = '#movie_player .videoAdUiAttribution'
+ wait = Wait(self.marionette, timeout=5)
+ try:
+ with self.marionette.using_context('content'):
+ wait.until(expected.element_present(By.CSS_SELECTOR,
+ selector))
+ countdown = self.marionette.find_element(By.CSS_SELECTOR,
+ selector)
+ ad_time = self._time_pattern.search(countdown.text)
+ if ad_time:
+ ad_minutes = int(ad_time.group('minute'))
+ ad_seconds = int(ad_time.group('second'))
+ return 60 * ad_minutes + ad_seconds
+ except (TimeoutException, NoSuchElementException):
+ self.marionette.log('Could not obtain '
+ 'element: %s' % selector,
+ level='WARNING')
+ return None
+
+ @property
+ def player_stalled(self):
+ """
+ :return True if playback is not making progress for 4-9 seconds. This
+ excludes ad breaks.
+
+ Note that the player might just be busy with buffering due to a slow
+ network.
+ """
+ # `current_time` stands still while ad is playing
+ def condition():
+ # no ad is playing and current_time stands still
+ return (not self.ad_playing and
+ self.measure_progress() < 0.1 and
+ self.player_measure_progress() < 0.1 and
+ (self.player_playing or self.player_buffering))
+
+ if condition():
+ sleep(2)
+ if self.player_buffering:
+ sleep(5)
+ return condition()
+ else:
+ return False
+
+ def deactivate_autoplay(self):
+ """
+ Attempt to turn off autoplay. Return True if successful.
+ """
+ element_id = 'autoplay-checkbox'
+ mn = self.marionette
+ wait = Wait(mn, timeout=10)
+
+ def get_status(el):
+ script = 'return arguments[0].wrappedJSObject.checked'
+ return mn.execute_script(script, script_args=[el])
+
+ try:
+ with mn.using_context('content'):
+ # the width, height of the element are 0, so it's not visible
+ wait.until(expected.element_present(By.ID, element_id))
+ checkbox = mn.find_element(By.ID, element_id)
+
+ # Note: in some videos, due to late-loading of sidebar ads, the
+ # button is rerendered after sidebar ads appear & the autoplay
+ # pref resets to "on". In other words, if you click too early,
+ # the pref might get reset moments later.
+ sleep(1)
+ if get_status(checkbox):
+ mn.execute_script('return arguments[0].'
+ 'wrappedJSObject.click()',
+ script_args=[checkbox])
+ self.marionette.log('Toggled autoplay.')
+ autoplay = get_status(checkbox)
+ self.marionette.log('Autoplay is %s' % autoplay)
+ return (autoplay is not None) and (not autoplay)
+ except (NoSuchElementException, TimeoutException):
+ return False
+
+ def __str__(self):
+ messages = [super(YouTubePuppeteer, self).__str__()]
+ if self.player:
+ player_state = self._yt_player_state_name[self.player_state]
+ ad_state = self._yt_player_state_name[self.ad_state]
+ messages += [
+ '#movie_player: {',
+ '\tvideo id: {0},'.format(self.movie_id),
+ '\tvideo_title: {0}'.format(self.movie_title),
+ '\tcurrent_state: {0},'.format(player_state),
+ '\tad_state: {0},'.format(ad_state),
+ '\tplayback_quality: {0},'.format(self.playback_quality),
+ '\tcurrent_time: {0},'.format(self.player_current_time),
+ '\tduration: {0},'.format(self.player_duration),
+ '}'
+ ]
+ else:
+ messages += ['\t#movie_player: None']
+ return '\n'.join(messages)
+
+
+def playback_started(yt):
+ """
+ Check whether playback has started.
+ :param yt: YouTubePuppeteer
+ """
+ # usually, ad is playing during initial buffering
+ if (yt.player_state in
+ [yt._yt_player_state['PLAYING'],
+ yt._yt_player_state['BUFFERING']]):
+ return True
+ if yt.current_time > 0 or yt.player_current_time > 0:
+ return True
+ return False
+
+
+def playback_done(yt):
+ """
+ Check whether playback is done, skipping ads if possible.
+ :param yt: YouTubePuppeteer
+ """
+ # in case ad plays at end of video
+ if yt.ad_state == yt._yt_player_state['PLAYING']:
+ yt.attempt_ad_skip()
+ return False
+ return yt.player_ended or yt.player_remaining_time < 1
+
+
+def wait_for_almost_done(yt, final_piece=120):
+ """
+ Allow the given video to play until only `final_piece` seconds remain,
+ skipping ads mid-way as much as possible.
+ `final_piece` should be short enough to not be interrupted by an ad.
+
+ Depending on the length of the video, check the ad status every 10-30
+ seconds, skip an active ad if possible.
+
+ :param yt: YouTubePuppeteer
+ """
+ rest = 10
+ duration = remaining_time = yt.expected_duration
+ if duration < final_piece:
+ # video is short so don't attempt to skip more ads
+ return duration
+ elif duration > 600:
+ # for videos that are longer than 10 minutes
+ # wait longer between checks
+ rest = duration/50
+
+ while remaining_time > final_piece:
+ if yt.player_stalled:
+ if yt.player_buffering:
+ # fall back on timeout in 'wait' call that comes after this
+ # in test function
+ yt.marionette.log('Buffering and no playback progress.')
+ break
+ else:
+ message = '\n'.join(['Playback stalled', str(yt)])
+ raise VideoException(message)
+ if yt.breaks_count > 0:
+ yt.process_ad()
+ if remaining_time > 1.5 * rest:
+ sleep(rest)
+ else:
+ sleep(rest/2)
+ # TODO during an ad, remaining_time will be based on ad's current_time
+ # rather than current_time of target video
+ remaining_time = yt.player_remaining_time
+ return remaining_time
new file mode 100644
--- /dev/null
+++ b/dom/media/test/external-media-tests/requirements.txt
@@ -0,0 +1,22 @@
+browsermob-proxy==0.6.0
+manifestparser==1.1
+mozcrash==0.16
+mozdevice==0.46
+mozfile==1.2
+mozhttpd==0.7
+mozinfo==0.8
+# optional - mozharness install step
+mozInstall==1.12
+mozlog==3.0
+moznetwork==0.27
+mozprocess==0.22
+mozprofile==0.27
+mozrunner==6.9
+moztest==0.7
+mozversion==1.4
+marionette-client == 2.0.0
+marionette-driver == 1.1.1
+firefox-puppeteer==3.1.0
+
+# Install the firefox media tests package
+./
new file mode 100644
--- /dev/null
+++ b/dom/media/test/external-media-tests/setup.py
@@ -0,0 +1,42 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from setuptools import setup, find_packages
+
+PACKAGE_VERSION = '0.4'
+
+deps = [
+ 'marionette-client == 2.0.0',
+ 'marionette-driver == 1.1.1',
+ 'mozlog == 3.0',
+ 'manifestparser == 1.1',
+ 'firefox-puppeteer >= 3.1.0, <4.0.0',
+]
+
+setup(name='firefox-media-tests',
+ version=PACKAGE_VERSION,
+ description=('A collection of Mozilla Firefox media playback tests run '
+ 'with Marionette'),
+ classifiers=[
+ 'Environment :: Console',
+ 'Intended Audience :: Developers',
+ 'License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)',
+ 'Natural Language :: English',
+ 'Operating System :: OS Independent',
+ 'Programming Language :: Python',
+ 'Topic :: Software Development :: Libraries :: Python Modules',
+ ],
+ keywords='mozilla',
+ author='Mozilla Automation and Tools Team',
+ author_email='tools@lists.mozilla.org',
+ url='https://github.com/mjzffr/firefox-media-tests',
+ license='MPL 2.0',
+ packages=find_packages(),
+ zip_safe=False,
+ install_requires=deps,
+ include_package_data=True,
+ entry_points="""
+ [console_scripts]
+ firefox-media-tests = harness:cli
+ """)