Chromium Code Reviews| Index: third_party/WebKit/LayoutTests/media/media-play-promise.html |
| diff --git a/third_party/WebKit/LayoutTests/media/media-play-promise.html b/third_party/WebKit/LayoutTests/media/media-play-promise.html |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..2c18fbb70f37deaf5133d8b68c619af8e7f8004d |
| --- /dev/null |
| +++ b/third_party/WebKit/LayoutTests/media/media-play-promise.html |
| @@ -0,0 +1,390 @@ |
| +<html> |
| +<head> |
| +<script src=media-file.js></script> |
| +<script src=video-test.js></script> |
|
philipj_slow
2016/02/22 06:34:24
Can you add a TODO(mlamouri) in the style of https
mlamouri (slow - plz ping)
2016/02/23 16:48:59
Done.
|
| + |
| +<script> |
| + // This is testing the behavior of play() with regards to the returned |
| + // promise. This test file is creating a small framework in order to be able |
| + // to test different cases easily and independently of each other. |
| + // |
| + // All tests have to be part of the TESTS array. When the page is loaded, |
| + // first function in the array is ran. A test is considered done when the |
|
philipj_slow
2016/02/22 06:34:24
s/ran/run/ here and below
mlamouri (slow - plz ping)
2016/02/23 16:48:59
Done.
|
| + // promise returned by mediaElement.play() is resolved or rejected. Each |
| + // test then needs to call play() once which wraps this logic. When a test |
| + // is finished, the next test in the array is ran until the entire array |
| + // was processed. |
| + // |
| + // Each test should start by prting its name in order to facilitate reading |
|
philipj_slow
2016/02/22 06:34:24
s/prting/printing/
mlamouri (slow - plz ping)
2016/02/23 16:49:00
Done.
|
| + // the output. |
| + |
| + function runNextTestOrFinish() |
| + { |
| + currentTest++; |
| + if (currentTest >= TESTS.length) { |
| + endTest(); |
| + return; |
| + } |
| + |
| + consoleWrite(""); |
| + TESTS[currentTest](); |
| + } |
| + |
| + function play() |
| + { |
| + return mediaElement.play().then(function() { |
|
philipj_slow
2016/02/22 06:34:24
The return value is never used, skip return to mak
mlamouri (slow - plz ping)
2016/02/23 16:48:59
Done.
|
| + consoleWrite("Promise Resolved"); |
|
philipj_slow
2016/02/22 06:34:24
Verify arguments.length == 1 and arguments[0]===un
mlamouri (slow - plz ping)
2016/02/23 16:49:00
Done.
|
| + }, function(e) { |
| + consoleWrite("Promise Failed with " + e.name); |
|
philipj_slow
2016/02/22 06:34:24
Verify arguments.length == 1, e instanceof DOMExce
mlamouri (slow - plz ping)
2016/02/23 16:49:00
Done.
|
| + }).then(runNextTestOrFinish); |
| + } |
| + |
| + function playWithUserGesture() |
| + { |
| + if (!window.eventSender) { |
|
philipj_slow
2016/02/22 06:34:24
If we're not running under LayoutTests we will hav
mlamouri (slow - plz ping)
2016/02/23 16:48:59
Done.
|
| + failTest("No window.eventSender"); |
| + return; |
| + } |
| + |
| + var target = document.querySelector("p"); |
| + target.onclick = function() { |
| + play(); |
| + target.onclick = null; |
| + }; |
| + |
| + var boundingRect = target.getBoundingClientRect(); |
| + var x = boundingRect.left + (boundingRect.width / 2); |
| + var y = boundingRect.top + (boundingRect.height / 2); |
| + |
| + eventSender.mouseMoveTo(x, y); |
| + eventSender.mouseDown(); |
| + eventSender.mouseUp(); |
| + } |
| + |
| + var currentTest = -1; |
| + |
| + var TESTS = [ |
| + // Test that play() on an element that is currently loading returns a |
| + // promise which resolves successfuly. |
| + function playLoading() |
| + { |
| + consoleWrite("playLoading()"); |
| + internals.settings.setMediaPlaybackRequiresUserGesture(false); |
| + |
| + run("mediaElement = document.createElement('audio')"); |
| + var mediaFile = findMediaFile("audio", "content/test"); |
| + run("mediaElement.src = '" + mediaFile + "'"); |
| + |
| + waitForEvent('playing'); |
| + play(); |
|
philipj_slow
2016/02/22 06:34:24
Verify the conditions for "currently loading" that
mlamouri (slow - plz ping)
2016/02/23 16:49:00
Already there.
|
| + }, |
| + |
| + // Test that play() on an element that is already loaded returns a |
|
philipj_slow
2016/02/22 06:34:24
This can be rephrased as testing the "notify about
mlamouri (slow - plz ping)
2016/02/23 16:49:00
Done, apart from the promise thing. The play() pro
philipj_slow
2016/02/24 09:38:27
Sorry about that, you're right.
|
| + // promise which which resolves successfuly. |
| + function playLoaded() |
| + { |
| + consoleWrite("playLoaded()"); |
| + internals.settings.setMediaPlaybackRequiresUserGesture(false); |
| + |
| + run("mediaElement = document.createElement('audio')"); |
| + var mediaFile = findMediaFile("audio", "content/test"); |
| + run("mediaElement.src = '" + mediaFile + "'"); |
| + |
| + waitForEvent('playing'); |
| + |
| + waitForEvent('canplaythrough', function() { |
| + testExpected(HTMLMediaElement.HAVE_ENOUGH_DATA, mediaElement.readyState); |
| + testExpected(true, mediaElement.paused) |
| + |
| + play(); |
| + }); |
| + |
| + mediaElement.load(); |
|
philipj_slow
2016/02/22 06:34:25
Calling load() in addition to setting src doesn't
mlamouri (slow - plz ping)
2016/02/23 16:49:00
Done.
|
| + }, |
| + |
| + // Test that play() on an element when media playback requires a gesture |
| + // returns a rejected promise if there is no user gesture. |
|
philipj_slow
2016/02/22 06:34:24
Looks like the description should be switched with
mlamouri (slow - plz ping)
2016/02/23 16:49:00
Done.
|
| + function playRequiresUserGestureAndHasIt() |
| + { |
| + consoleWrite("playRequiresUserGestureAndHasIt()"); |
| + internals.settings.setMediaPlaybackRequiresUserGesture(true); |
| + |
| + run("mediaElement = document.createElement('audio')"); |
| + var mediaFile = findMediaFile("audio", "content/test"); |
| + run("mediaElement.src = '" + mediaFile + "'"); |
| + |
| + waitForEvent('playing'); |
| + playWithUserGesture(); |
| + }, |
| + |
| + // Test that play() on an element when media playback requires a gesture |
|
philipj_slow
2016/02/22 06:34:24
Switch description with above.
mlamouri (slow - plz ping)
2016/02/23 16:49:00
Done.
|
| + // returns a resolved promise if there is a user gesture. |
| + function playRequiresUserGestureAndDoesNotHaveIt() |
| + { |
| + consoleWrite("playRequiresUserGestureAndDoesNotHaveIt()"); |
| + internals.settings.setMediaPlaybackRequiresUserGesture(true); |
| + |
| + run("mediaElement = document.createElement('audio')"); |
| + var mediaFile = findMediaFile("audio", "content/test"); |
| + run("mediaElement.src = '" + mediaFile + "'"); |
| + |
| + waitForEvent('playing'); |
| + play(); |
| + }, |
| + |
| + // Test that play() on an element with an unsupported content will |
| + // return a rejected promise. |
| + function playNotSupportedContent() |
| + { |
| + consoleWrite("playNotSupportedContent()"); |
| + internals.settings.setMediaPlaybackRequiresUserGesture(false); |
| + |
| + run("mediaElement = document.createElement('audio')"); |
| + var mediaFile = findMediaFile("audio", "content/garbage"); |
|
philipj_slow
2016/02/22 06:34:24
content/garbage doesn't exist. "data:," is a fine
mlamouri (slow - plz ping)
2016/02/23 16:49:00
Done.
|
| + run("mediaElement.src = '" + mediaFile + "'"); |
| + |
| + waitForEvent('playing'); |
| + waitForEvent('error', function() { |
| + testExpected("mediaElement.error", "[object MediaError]"); |
| + testExpected("mediaElement.error.code", MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED); |
| + }); |
| + play(); |
| + }, |
| + |
| + // Test that play() returns a resolved promise if called after the |
| + // element suffered from a decode error. |
| + function playDecodeError() |
| + { |
| + consoleWrite("playDecodeError()"); |
| + internals.settings.setMediaPlaybackRequiresUserGesture(false); |
| + |
| + run("mediaElement = document.createElement('audio')"); |
| + var mediaFile = findMediaFile("audio", "content/test"); |
| + run("mediaElement.src = '" + mediaFile + "'"); |
| + |
| + waitForEvent('playing'); |
| + waitForEvent('error', function() { |
| + testExpected("mediaElement.error", "[object MediaError]"); |
| + testExpected("mediaElement.error.code", MediaError.MEDIA_ERR_DECODE); |
| + }); |
| + |
| + // The setMediaElementNetworkState() method requires metadata to be |
| + // available. |
| + waitForEvent('loadedmetadata', function() { |
| + internals.setMediaElementNetworkState(mediaElement, 6 /* NetworkStateDecodeError */); |
| + play(); |
|
philipj_slow
2016/02/22 06:34:24
This doesn't map to any real-world situation AFAIC
mlamouri (slow - plz ping)
2016/02/23 16:49:00
Ack.
|
| + }); |
| + }, |
| + |
| + // Test that play() returns a resolved promise if called after the |
|
philipj_slow
2016/02/22 06:34:24
IMHO it would be more interesting to see what happ
mlamouri (slow - plz ping)
2016/02/23 16:49:00
I've added two comments. I guess the reason why I
philipj_slow
2016/02/24 09:38:27
Agree, this is OK for now. We should probably revi
|
| + // element suffered from a network error. |
| + function playNetworkError() |
| + { |
| + consoleWrite("playNetworkError()"); |
| + internals.settings.setMediaPlaybackRequiresUserGesture(false); |
| + |
| + run("mediaElement = document.createElement('audio')"); |
| + var mediaFile = findMediaFile("audio", "content/test"); |
| + run("mediaElement.src = '" + mediaFile + "'"); |
| + |
| + waitForEvent('playing'); |
| + waitForEvent('error', function() { |
| + testExpected("mediaElement.error", "[object MediaError]"); |
| + testExpected("mediaElement.error.code", MediaError.MEDIA_ERR_NETWORK); |
| + }); |
| + |
| + // The setMediaElementNetworkState() method requires metadata to be |
| + // available. |
| + waitForEvent('loadedmetadata', function() { |
| + internals.setMediaElementNetworkState(mediaElement, 5 /* NetworkStateNetworkError */); |
| + play(); |
| + }); |
| + }, |
| + |
| + // Test that play() returns a rejected promise if the element is |
| + // suferring from a not supported error. |
| + function playWithErrorAlreadySet() |
| + { |
| + consoleWrite("playWithErrorAlreadySet()"); |
| + internals.settings.setMediaPlaybackRequiresUserGesture(false); |
| + |
| + run("mediaElement = document.createElement('audio')"); |
| + var mediaFile = findMediaFile("audio", "content/garbage"); |
|
philipj_slow
2016/02/22 06:34:24
data:, here and everywhere content/garbage is used
mlamouri (slow - plz ping)
2016/02/23 16:49:00
Done.
|
| + run("mediaElement.src = '" + mediaFile + "'"); |
| + |
| + run("mediaElement.load()"); |
| + |
| + waitForEvent('playing'); |
| + waitForEvent('error', function() { |
| + testExpected("mediaElement.error", "[object MediaError]"); |
| + testExpected("mediaElement.error.code", MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED); |
| + play(); |
| + }); |
| + }, |
| + |
| + // Test that play() returns a resolved promise if the element had its |
| + // source changed after suffering from an error. |
| + function playSrcChangedAfterError() |
| + { |
| + consoleWrite("playSrcChangedAfterError()"); |
| + internals.settings.setMediaPlaybackRequiresUserGesture(false); |
| + |
| + run("mediaElement = document.createElement('audio')"); |
| + var mediaFile = findMediaFile("audio", "content/garbage"); |
| + run("mediaElement.src = '" + mediaFile + "'"); |
| + |
| + run("mediaElement.load()"); |
| + |
| + waitForEvent('error', function() { |
| + testExpected("mediaElement.error", "[object MediaError]"); |
| + testExpected("mediaElement.error.code", MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED); |
| + |
| + mediaFile = findMediaFile("audio", "content/test"); |
| + run("mediaElement.src = '" + mediaFile + "'"); |
| + |
| + waitForEvent('playing'); |
| + waitForEvent('loadedmetadata', function() { |
| + play(); |
| + }); |
| + }); |
| + }, |
| + |
| + // Test that play() returns a rejected promise if the element had an |
|
philipj_slow
2016/02/22 06:34:24
You already test that the promise rejects when err
mlamouri (slow - plz ping)
2016/02/23 16:49:00
There are 4 src_not_supported related tests:
1. pl
philipj_slow
2016/02/24 09:38:27
Aha, well I think you've found a bit of a problem
|
| + // error and just changed its source. |
| + function playRaceWithSrcChangeError() |
| + { |
| + consoleWrite("playRaceWithSrcChangeError()"); |
| + internals.settings.setMediaPlaybackRequiresUserGesture(false); |
| + |
| + run("mediaElement = document.createElement('audio')"); |
| + var mediaFile = findMediaFile("audio", "content/garbage"); |
| + run("mediaElement.src = '" + mediaFile + "'"); |
| + |
| + run("mediaElement.load()"); |
| + |
| + waitForEvent('error', function() { |
| + testExpected("mediaElement.error", "[object MediaError]"); |
| + testExpected("mediaElement.error.code", MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED); |
| + |
| + mediaFile = findMediaFile("audio", "content/test"); |
| + run("mediaElement.src = '" + mediaFile + "'"); |
| + |
| + // TODO(mlamouri): if we print the 'playing' event, it seems |
| + // that it actually happens later. It's unclear why. |
|
philipj_slow
2016/02/22 06:34:24
This sounds like it could be the task ordering pro
mlamouri (slow - plz ping)
2016/02/23 16:48:59
Hmm, not sure if this comment is clear. Also it's
philipj_slow
2016/02/24 09:38:27
Right, setting the src should clear the error, and
mlamouri (slow - plz ping)
2016/02/25 11:07:35
As discussed on IRC, the issue was that the tasks
|
| + play(); |
| + }); |
| + }, |
| + |
| + // Test that play() returns a resolved promise when calling play() then |
| + // pause() on an element that already has enough data to play. In other |
| + // words, pause() doesn't cancel play() because it was resolved |
|
philipj_slow
2016/02/22 06:34:24
Right, this is an important case that will potenti
mlamouri (slow - plz ping)
2016/02/23 16:49:00
I tend to prefer keeping tests that look redundant
philipj_slow
2016/02/24 09:38:27
Acknowledged.
|
| + // immediately. |
| + function playFollowedByPauseWhenLoaded() |
|
philipj_slow
2016/02/22 06:34:24
I suggest avoiding "loaded" as there's no readySta
mlamouri (slow - plz ping)
2016/02/23 16:49:00
Done.
|
| + { |
| + consoleWrite("playFollowedByPauseWhenLoaded()"); |
| + internals.settings.setMediaPlaybackRequiresUserGesture(false); |
| + |
| + run("mediaElement = document.createElement('audio')"); |
| + var mediaFile = findMediaFile("audio", "content/test"); |
| + run("mediaElement.src = '" + mediaFile + "'"); |
| + |
| + run("mediaElement.load()"); |
| + |
| + waitForEvent('canplaythrough', function() { |
| + waitForEvent('playing'); |
| + testExpected("mediaElement.readyState", HTMLMediaElement.HAVE_ENOUGH_DATA); |
| + play(); |
| + testExpected("mediaElement.paused", false); |
| + mediaElement.pause(); |
| + testExpected("mediaElement.paused", true); |
| + }); |
| + }, |
| + |
| + // Test that play() returns a rejected promise when calling play() then |
| + // pause() on an element that doesn't have enough data to play. In other |
| + // words, pause() cancels play() before it can be resolved. |
| + function playFollowedByPauseWhenLoading() |
|
philipj_slow
2016/02/22 06:34:24
playAndPauseBeforeCanPlay if you change the naming
mlamouri (slow - plz ping)
2016/02/23 16:49:00
Done.
|
| + { |
| + consoleWrite("playFollowedByPauseWhenLoaded()"); |
| + internals.settings.setMediaPlaybackRequiresUserGesture(false); |
| + |
| + run("mediaElement = document.createElement('audio')"); |
| + var mediaFile = findMediaFile("audio", "content/test"); |
| + run("mediaElement.src = '" + mediaFile + "'"); |
|
philipj_slow
2016/02/22 06:34:25
This test shouldn't need to set a src at all. Tryi
mlamouri (slow - plz ping)
2016/02/23 16:49:00
Done.
|
| + |
| + waitForEvent('playing'); |
| + testExpected("mediaElement.readyState", HTMLMediaElement.HAVE_NOTHING); |
| + play(); |
| + testExpected("mediaElement.paused", false); |
| + mediaElement.pause(); |
| + testExpected("mediaElement.paused", true); |
| + }, |
| + |
| + // Test that load() rejects all the pending play() promises. |
| + function loadRejectPendingPromises() |
|
philipj_slow
2016/02/22 06:34:24
Rejects with an s
mlamouri (slow - plz ping)
2016/02/23 16:49:00
Done.
|
| + { |
| + consoleWrite("loadRejectPendingPromises()"); |
| + internals.settings.setMediaPlaybackRequiresUserGesture(false); |
| + |
| + run("mediaElement = document.createElement('audio')"); |
| + |
| + play(); // the promise will be left pending. |
| + |
| + waitForEvent('playing'); |
| + run("mediaElement.load()"); |
| + }, |
| + |
| + // Test that changing the src rejects the pending play() promises. |
|
philipj_slow
2016/02/22 06:34:24
Note that this is a paranoid test or remove it. Th
mlamouri (slow - plz ping)
2016/02/23 16:48:59
Done.
philipj_slow
2016/02/24 09:38:27
Not done, but that's OK :)
|
| + function newSrcRejectPendingPromises() |
| + { |
| + consoleWrite("newSrcRejectPendingPromises()"); |
| + internals.settings.setMediaPlaybackRequiresUserGesture(false); |
| + |
| + run("mediaElement = document.createElement('audio')"); |
| + |
| + play(); // the promise will be left pending. |
| + |
| + var mediaFile = findMediaFile("audio", "content/test"); |
| + run("mediaElement.src = '" + mediaFile + "'"); |
| + }, |
| + |
| + // Test ordering of events and promises. |
| + // This is testing a bug in Blink, see https://crbug.com/587871 |
| + function testEventAndPromiseOrdering() |
| + { |
| + consoleWrite("testEventAndPromiseOrdering"); |
| + internals.settings.setMediaPlaybackRequiresUserGesture(false); |
| + |
| + var promiseResolver = null; |
| + var p = new Promise(function(resolve, reject) { |
| + promiseResolver = resolve; |
| + }); |
| + p.then(function() { |
| + consoleWrite("Should be after the play() promise is resolved"); |
|
philipj_slow
2016/02/22 06:34:24
I think this test is not quite right. Per spec the
mlamouri (slow - plz ping)
2016/02/23 16:49:00
Done.
|
| + }); |
| + |
| + run("mediaElement = document.createElement('audio')"); |
| + var mediaFile = findMediaFile("audio", "content/test"); |
| + run("mediaElement.src = '" + mediaFile + "'"); |
| + |
| + play(); |
| + waitForEvent('playing', function() { |
| + promiseResolver(); |
| + }); |
| + } |
| + ]; |
| + |
| + function start() |
| + { |
| + runNextTestOrFinish(); |
| + } |
| + |
| +</script> |
| +</head> |
| + |
| +<body onload="start()"> |
| + |
| +<p>Test the play() behaviour with regards to the returned promise for media elements.</p> |
| + |
| +</body> |
| +</html> |