Bug 1260084 - Part2 - Copy css animation mochitest to web-platform tests. r=birtles
☠☠ backed out by 56148c26e7a1 ☠ ☠
authorMantaroh Yoshinaga <mantaroh@gmail.com>
Mon, 18 Apr 2016 13:15:18 +0900
changeset 331720 0e54229af7f4ab003fb0572926ee9a2e283e6d35
parent 331719 b72de5c5ac3363c5fe7dc94c74a13a1152720100
child 331721 545b4ff9a8e022658fc21ccf4fbad47ac30c97ee
push id6048
push userkmoir@mozilla.com
push dateMon, 06 Jun 2016 19:02:08 +0000
treeherdermozilla-beta@46d72a56c57d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbirtles
bugs1260084
milestone48.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1260084 - Part2 - Copy css animation mochitest to web-platform tests. r=birtles MozReview-Commit-ID: BVEUDA9fzyI
testing/web-platform/meta/MANIFEST.json
testing/web-platform/tests/web-animations/animation/finished.html
testing/web-platform/tests/web-animations/animation/id.html
testing/web-platform/tests/web-animations/animation/oncancel.html
testing/web-platform/tests/web-animations/animation/onfinish.html
testing/web-platform/tests/web-animations/animation/pause.html
testing/web-platform/tests/web-animations/animation/ready.html
testing/web-platform/tests/web-animations/animation/reverse.html
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -28796,16 +28796,44 @@
       {
         "path": "web-animations/animation/constructor.html",
         "url": "/web-animations/animation/constructor.html"
       },
       {
         "path": "web-animations/animation/finish.html",
         "url": "/web-animations/animation/finish.html"
       },
+       {
+        "path": "web-animations/animation/finished.html",
+        "url": "/web-animations/animation/finished.html"
+      },
+      {
+        "path": "web-animations/animation/id.html",
+        "url": "/web-animations/animation/id.html"
+      },
+      {
+        "path": "web-animations/animation/oncancel.html",
+        "url": "/web-animations/animation/oncancel.html"
+      },
+      {
+       "path": "web-animations/animation/onfinish.html",
+        "url": "/web-animations/animation/onfinish.html"
+      },
+      {
+        "path": "web-animations/animation/pause.html",
+        "url": "/web-animations/animation/pause.html"
+      },
+      {
+        "path": "web-animations/animation/ready.html",
+        "url": "/web-animations/animation/ready.html"
+      },
+      {
+        "path": "web-animations/animation/reverse.html",
+        "url": "/web-animations/animation/reverse.html"
+      },
       {
         "path": "web-animations/animation/play.html",
         "url": "/web-animations/animation/play.html"
       },
       {
         "path": "web-animations/animation/playState.html",
         "url": "/web-animations/animation/playState.html"
       },
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/web-animations/animation/finished.html
@@ -0,0 +1,371 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Animation.finished</title>
+<link rel="help" href="https://w3c.github.io/web-animations/#dom-animation-finished">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../testcommon.js"></script>
+<link rel="stylesheet" href="/resources/testharness.css">
+<body>
+<div id="log"></div>
+<script>
+"use strict";
+
+promise_test(function(t) {
+  var div = createDiv(t);
+  var animation = div.animate({}, 100 * MS_PER_SEC);
+  var previousFinishedPromise = animation.finished;
+  return animation.ready.then(function() {
+    assert_equals(animation.finished, previousFinishedPromise,
+                  'Finished promise is the same object when playing starts');
+    animation.pause();
+    assert_equals(animation.finished, previousFinishedPromise,
+                  'Finished promise does not change when pausing');
+    animation.play();
+    assert_equals(animation.finished, previousFinishedPromise,
+                  'Finished promise does not change when play() unpauses');
+
+    animation.currentTime = 100 * MS_PER_SEC;
+
+    return animation.finished;
+  }).then(function() {
+    assert_equals(animation.finished, previousFinishedPromise,
+                  'Finished promise is the same object when playing completes');
+  });
+}, 'Test pausing then playing does not change the finished promise');
+
+promise_test(function(t) {
+  var div = createDiv(t);
+  var animation = div.animate({}, 100 * MS_PER_SEC);
+  var previousFinishedPromise = animation.finished;
+  animation.finish();
+  return animation.finished.then(function() {
+    assert_equals(animation.finished, previousFinishedPromise,
+                  'Finished promise is the same object when playing completes');
+    animation.play();
+    assert_not_equals(animation.finished, previousFinishedPromise,
+                  'Finished promise changes when replaying animation');
+
+    previousFinishedPromise = animation.finished;
+    animation.play();
+    assert_equals(animation.finished, previousFinishedPromise,
+                  'Finished promise is the same after redundant play() call');
+
+  });
+}, 'Test restarting a finished animation');
+
+promise_test(function(t) {
+  var div = createDiv(t);
+  var animation = div.animate({}, 100 * MS_PER_SEC);
+  var previousFinishedPromise;
+  animation.finish();
+  return animation.finished.then(function() {
+    previousFinishedPromise = animation.finished;
+    animation.playbackRate = -1;
+    assert_not_equals(animation.finished, previousFinishedPromise,
+                      'Finished promise should be replaced when reversing a ' +
+                      'finished promise');
+    animation.currentTime = 0;
+    return animation.finished;
+  }).then(function() {
+    previousFinishedPromise = animation.finished;
+    animation.play();
+    assert_not_equals(animation.finished, previousFinishedPromise,
+                      'Finished promise is replaced after play() call on ' +
+                      'finished, reversed animation');
+  });
+}, 'Test restarting a reversed finished animation');
+
+promise_test(function(t) {
+  var div = createDiv(t);
+  var animation = div.animate({}, 100 * MS_PER_SEC);
+  var previousFinishedPromise = animation.finished;
+  animation.finish();
+  return animation.finished.then(function() {
+    animation.currentTime = 100 * MS_PER_SEC + 1000;
+    assert_equals(animation.finished, previousFinishedPromise,
+                  'Finished promise is unchanged jumping past end of ' +
+                  'finished animation');
+  });
+}, 'Test redundant finishing of animation');
+
+promise_test(function(t) {
+  var div = createDiv(t);
+  var animation = div.animate({}, 100 * MS_PER_SEC);
+  // Setup callback to run if finished promise is resolved
+  var finishPromiseResolved = false;
+  animation.finished.then(function() {
+    finishPromiseResolved = true;
+  });
+  return animation.ready.then(function() {
+    // Jump to mid-way in interval and pause
+    animation.currentTime = 100 * MS_PER_SEC / 2;
+    animation.pause();
+    return animation.ready;
+  }).then(function() {
+    // Jump to the end
+    // (But don't use finish() since that should unpause as well)
+    animation.currentTime = 100 * MS_PER_SEC;
+    return waitForAnimationFrames(2);
+  }).then(function() {
+    assert_false(finishPromiseResolved,
+                 'Finished promise should not resolve when paused');
+  });
+}, 'Finished promise does not resolve when paused');
+
+promise_test(function(t) {
+  var div = createDiv(t);
+  var animation = div.animate({}, 100 * MS_PER_SEC);
+  // Setup callback to run if finished promise is resolved
+  var finishPromiseResolved = false;
+  animation.finished.then(function() {
+    finishPromiseResolved = true;
+  });
+  return animation.ready.then(function() {
+    // Jump to mid-way in interval and pause
+    animation.currentTime = 100 * MS_PER_SEC / 2;
+    animation.pause();
+    // Jump to the end
+    animation.currentTime = 100 * MS_PER_SEC;
+    return waitForAnimationFrames(2);
+  }).then(function() {
+    assert_false(finishPromiseResolved,
+                 'Finished promise should not resolve when pause-pending');
+  });
+}, 'Finished promise does not resolve when pause-pending');
+
+promise_test(function(t) {
+  var div = createDiv(t);
+  var animation = div.animate({}, 100 * MS_PER_SEC);
+  animation.finish();
+  return animation.finished.then(function(resolvedAnimation) {
+    assert_equals(resolvedAnimation, animation,
+                  'Object identity of animation passed to Promise callback'
+                  + ' matches the animation object owning the Promise');
+  });
+}, 'The finished promise is fulfilled with its Animation');
+
+promise_test(function(t) {
+  var div = createDiv(t);
+  var animation = div.animate({}, 100 * MS_PER_SEC);
+  var previousFinishedPromise = animation.finished;
+
+  // Set up listeners on finished promise
+  var retPromise = animation.finished.then(function() {
+    assert_unreached('finished promise was fulfilled');
+  }).catch(function(err) {
+    assert_equals(err.name, 'AbortError',
+                  'finished promise is rejected with AbortError');
+    assert_not_equals(animation.finished, previousFinishedPromise,
+                      'Finished promise should change after the original is ' +
+                      'rejected');
+  });
+
+  animation.cancel();
+
+  return retPromise;
+}, 'finished promise is rejected when an animation is cancelled by calling ' +
+   'cancel()');
+
+promise_test(function(t) {
+  var div = createDiv(t);
+  var animation = div.animate({}, 100 * MS_PER_SEC);
+  var previousFinishedPromise = animation.finished;
+  animation.finish();
+  return animation.finished.then(function() {
+    animation.cancel();
+    assert_not_equals(animation.finished, previousFinishedPromise,
+                      'A new finished promise should be created when'
+                      + ' cancelling a finished animation');
+  });
+}, 'cancelling an already-finished animation replaces the finished promise');
+
+promise_test(function(t) {
+  var div = createDiv(t);
+  var animation = div.animate({}, 100 * MS_PER_SEC);
+  animation.cancel();
+  // The spec says we still create a new finished promise and reject the old
+  // one even if we're already idle. That behavior might change, but for now
+  // test that we do that.
+  var retPromise = animation.finished.catch(function(err) {
+    assert_equals(err.name, 'AbortError',
+                  'finished promise is rejected with AbortError');
+  });
+
+  // Redundant call to cancel();
+  var previousFinishedPromise = animation.finished;
+  animation.cancel();
+  assert_not_equals(animation.finished, previousFinishedPromise,
+                    'A redundant call to cancel() should still generate a new'
+                    + ' finished promise');
+  return retPromise;
+}, 'cancelling an idle animation still replaces the finished promise');
+
+promise_test(function(t) {
+  var div = createDiv(t);
+  var animation = div.animate({}, 100 * MS_PER_SEC);
+  const HALF_DUR = 100 * MS_PER_SEC / 2;
+  const QUARTER_DUR = 100 * MS_PER_SEC / 4;
+  var gotNextFrame = false;
+  var currentTimeBeforeShortening;
+  animation.currentTime = HALF_DUR;
+  return animation.ready.then(function() {
+    currentTimeBeforeShortening = animation.currentTime;
+    animation.effect.timing.duration = QUARTER_DUR;
+    // Below we use gotNextFrame to check that shortening of the animation
+    // duration causes the finished promise to resolve, rather than it just
+    // getting resolved on the next animation frame. This relies on the fact
+    // that the promises are resolved as a micro-task before the next frame
+    // happens.
+    waitForAnimationFrames(1).then(function() {
+      gotNextFrame = true;
+    });
+
+    return animation.finished;
+  }).then(function() {
+    assert_false(gotNextFrame, 'shortening of the animation duration should ' +
+                               'resolve the finished promise');
+    assert_equals(animation.currentTime, currentTimeBeforeShortening,
+                  'currentTime should be unchanged when duration shortened');
+    var previousFinishedPromise = animation.finished;
+    animation.effect.timing.duration = 100 * MS_PER_SEC;
+    assert_not_equals(animation.finished, previousFinishedPromise,
+                      'Finished promise should change after lengthening the ' +
+                      'duration causes the animation to become active');
+  });
+}, 'Test finished promise changes for animation duration changes');
+
+promise_test(function(t) {
+  var div = createDiv(t);
+  var animation = div.animate({}, 100 * MS_PER_SEC);
+  var retPromise = animation.ready.then(function() {
+    animation.playbackRate = 0;
+    animation.currentTime = 100 * MS_PER_SEC + 1000;
+    return waitForAnimationFrames(2);
+  });
+
+  animation.finished.then(t.step_func(function() {
+    assert_unreached('finished promise should not resolve when playbackRate ' +
+                     'is zero');
+  }));
+
+  return retPromise;
+}, 'Test finished promise changes when playbackRate == 0');
+
+promise_test(function(t) {
+  var div = createDiv(t);
+  var animation = div.animate({}, 100 * MS_PER_SEC);
+  return animation.ready.then(function() {
+    animation.playbackRate = -1;
+    return animation.finished;
+  });
+}, 'Test finished promise resolves when reaching to the natural boundary.');
+
+promise_test(function(t) {
+  var div = createDiv(t);
+  var animation = div.animate({}, 100 * MS_PER_SEC);
+  var previousFinishedPromise = animation.finished;
+  animation.finish();
+  return animation.finished.then(function() {
+    animation.currentTime = 0;
+    assert_not_equals(animation.finished, previousFinishedPromise,
+                      'Finished promise should change once a prior ' +
+                      'finished promise resolved and the animation ' +
+                      'falls out finished state');
+  });
+}, 'Test finished promise changes when a prior finished promise resolved ' +
+   'and the animation falls out finished state');
+
+test(function(t) {
+  var div = createDiv(t);
+  var animation = div.animate({}, 100 * MS_PER_SEC);
+  var previousFinishedPromise = animation.finished;
+  animation.currentTime = 100 * MS_PER_SEC;
+  animation.currentTime = 100 * MS_PER_SEC / 2;
+  assert_equals(animation.finished, previousFinishedPromise,
+                'No new finished promise generated when finished state ' +
+                'is checked asynchronously');
+}, 'Test no new finished promise generated when finished state ' +
+   'is checked asynchronously');
+
+test(function(t) {
+  var div = createDiv(t);
+  var animation = div.animate({}, 100 * MS_PER_SEC);
+  var previousFinishedPromise = animation.finished;
+  animation.finish();
+  animation.currentTime = 100 * MS_PER_SEC / 2;
+  assert_not_equals(animation.finished, previousFinishedPromise,
+                    'New finished promise generated when finished state ' +
+                    'is checked synchronously');
+}, 'Test new finished promise generated when finished state ' +
+   'is checked synchronously');
+
+promise_test(function(t) {
+  var div = createDiv(t);
+  var animation = div.animate({}, 100 * MS_PER_SEC);
+  var resolvedFinished = false;
+  animation.finished.then(function() {
+    resolvedFinished = true;
+  });
+  return animation.ready.then(function() {
+    animation.finish();
+    animation.currentTime = 100 * MS_PER_SEC / 2;
+  }).then(function() {
+    assert_true(resolvedFinished,
+      'Animation.finished should be resolved even if ' +
+      'the finished state is changed soon');
+  });
+
+}, 'Test synchronous finished promise resolved even if finished state ' +
+   'is changed soon');
+
+promise_test(function(t) {
+  var div = createDiv(t);
+  var animation = div.animate({}, 100 * MS_PER_SEC);
+  var resolvedFinished = false;
+  animation.finished.then(function() {
+    resolvedFinished = true;
+  });
+
+  return animation.ready.then(function() {
+    animation.currentTime = 100 * MS_PER_SEC;
+    animation.finish();
+  }).then(function() {
+    assert_true(resolvedFinished,
+      'Animation.finished should be resolved soon after finish() is ' +
+      'called even if there are other asynchronous promises just before it');
+  });
+}, 'Test synchronous finished promise resolved even if asynchronous ' +
+   'finished promise happens just before synchronous promise');
+
+promise_test(function(t) {
+  var div = createDiv(t);
+  var animation = div.animate({}, 100 * MS_PER_SEC);
+  animation.finished.then(t.step_func(function() {
+    assert_unreached('Animation.finished should not be resolved');
+  }));
+
+  return animation.ready.then(function() {
+    animation.currentTime = 100 * MS_PER_SEC;
+    animation.currentTime = 100 * MS_PER_SEC / 2;
+  });
+}, 'Test finished promise is not resolved when the animation ' +
+   'falls out finished state immediately');
+
+promise_test(function(t) {
+  var div = createDiv(t);
+  var animation = div.animate({}, 100 * MS_PER_SEC);
+  return animation.ready.then(function() {
+    animation.currentTime = 100 * MS_PER_SEC;
+    animation.finished.then(t.step_func(function() {
+      assert_unreached('Animation.finished should not be resolved');
+    }));
+    animation.currentTime = 0;
+  });
+
+}, 'Test finished promise is not resolved once the animation ' +
+   'falls out finished state even though the current finished ' +
+   'promise is generated soon after animation state became finished');
+
+</script>
+</body>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/web-animations/animation/id.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Animation.id</title>
+<link rel="help" href="https://w3c.github.io/web-animations/#dom-animation-id">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../testcommon.js"></script>
+<link rel="stylesheet" href="/resources/testharness.css">
+<body>
+<div id="log"></div>
+<script>
+"use strict";
+
+test(function(t) {
+  var div = createDiv(t);
+  var animation = div.animate({}, 100 * MS_PER_SEC);
+  assert_equals(animation.id, '', 'id for CSS Animation is initially empty');
+  animation.id = 'anim'
+
+  assert_equals(animation.id, 'anim', 'animation.id reflects the value set');
+}, 'Animation.id for CSS Animations');
+
+</script>
+</body>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/web-animations/animation/oncancel.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Animation.oncancel</title>
+<link rel="help" href="https://w3c.github.io/web-animations/#dom-animation-oncancel">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../testcommon.js"></script>
+<link rel="stylesheet" href="/resources/testharness.css">
+<body>
+<div id="log"></div>
+<script>
+"use strict";
+
+async_test(function(t) {
+  var div = createDiv(t);
+  var animation = div.animate({}, 100 * MS_PER_SEC);
+  var finishedTimelineTime;
+  animation.finished.then().catch(function() {
+    finishedTimelineTime = animation.timeline.currentTime;
+  });
+
+  animation.oncancel = t.step_func_done(function(event) {
+    assert_equals(event.currentTime, null,
+      'event.currentTime should be null');
+    assert_equals(event.timelineTime, finishedTimelineTime,
+      'event.timelineTime should equal to the animation timeline ' +
+      'when finished promise is rejected');
+  });
+
+  animation.cancel();
+}, 'oncancel event is fired when animation.cancel() is called.');
+
+</script>
+</body>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/web-animations/animation/onfinish.html
@@ -0,0 +1,122 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Animation.onfinish</title>
+<link rel="help" href="https://w3c.github.io/web-animations/#dom-animation-onfinish">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../testcommon.js"></script>
+<link rel="stylesheet" href="/resources/testharness.css">
+<body>
+<div id="log"></div>
+<script>
+"use strict";
+
+async_test(function(t) {
+  var div = createDiv(t);
+  var animation = div.animate({}, 100 * MS_PER_SEC);
+  var finishedTimelineTime;
+  animation.finished.then(function() {
+    finishedTimelineTime = animation.timeline.currentTime;
+  });
+
+  animation.onfinish = t.step_func_done(function(event) {
+    assert_equals(event.currentTime, 0,
+      'event.currentTime should be zero');
+    assert_equals(event.timelineTime, finishedTimelineTime,
+      'event.timelineTime should equal to the animation timeline ' +
+      'when finished promise is resolved');
+  });
+
+  animation.playbackRate = -1;
+}, 'onfinish event is fired when the currentTime < 0 and ' +
+   'the playbackRate < 0');
+
+async_test(function(t) {
+  var div = createDiv(t);
+  var animation = div.animate({}, 100 * MS_PER_SEC);
+
+  var finishedTimelineTime;
+  animation.finished.then(function() {
+    finishedTimelineTime = animation.timeline.currentTime;
+  });
+
+  animation.onfinish = t.step_func_done(function(event) {
+    assert_equals(event.currentTime, 100 * MS_PER_SEC,
+      'event.currentTime should be the effect end');
+    assert_equals(event.timelineTime, finishedTimelineTime,
+      'event.timelineTime should equal to the animation timeline ' +
+      'when finished promise is resolved');
+  });
+
+  animation.currentTime = 100 * MS_PER_SEC;
+}, 'onfinish event is fired when the currentTime > 0 and ' +
+   'the playbackRate > 0');
+
+async_test(function(t) {
+  var div = createDiv(t);
+  var animation = div.animate({}, 100 * MS_PER_SEC);
+
+  var finishedTimelineTime;
+  animation.finished.then(function() {
+    finishedTimelineTime = animation.timeline.currentTime;
+  });
+
+  animation.onfinish = t.step_func_done(function(event) {
+    assert_equals(event.currentTime, 100 * MS_PER_SEC,
+      'event.currentTime should be the effect end');
+    assert_equals(event.timelineTime, finishedTimelineTime,
+      'event.timelineTime should equal to the animation timeline ' +
+      'when finished promise is resolved');
+  });
+
+  animation.finish();
+}, 'onfinish event is fired when animation.finish() is called');
+
+promise_test(function(t) {
+  var div = createDiv(t);
+  var animation = div.animate({}, 100 * MS_PER_SEC);
+
+  animation.onfinish = function(event) {
+    assert_unreached('onfinish event should not be fired');
+  };
+
+  animation.currentTime = 100 * MS_PER_SEC / 2;
+  animation.pause();
+
+  return animation.ready.then(function() {
+    animation.currentTime = 100 * MS_PER_SEC;
+    return waitForAnimationFrames(2);
+  });
+}, 'onfinish event is not fired when paused');
+
+promise_test(function(t) {
+  var div = createDiv(t);
+  var animation = div.animate({}, 100 * MS_PER_SEC);
+  animation.onfinish = function(event) {
+    assert_unreached('onfinish event should not be fired');
+  };
+
+  return animation.ready.then(function() {
+    animation.playbackRate = 0;
+    animation.currentTime = 100 * MS_PER_SEC;
+    return waitForAnimationFrames(2);
+  });
+}, 'onfinish event is not fired when the playbackRate is zero');
+
+promise_test(function(t) {
+  var div = createDiv(t);
+  var animation = div.animate({}, 100 * MS_PER_SEC);
+  animation.onfinish = function(event) {
+    assert_unreached('onfinish event should not be fired');
+  };
+
+  return animation.ready.then(function() {
+    animation.currentTime = 100 * MS_PER_SEC;
+    animation.currentTime = 100 * MS_PER_SEC / 2;
+    return waitForAnimationFrames(2);
+  });
+}, 'onfinish event is not fired when the animation falls out ' +
+   'finished state immediately');
+
+</script>
+</body>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/web-animations/animation/pause.html
@@ -0,0 +1,99 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Animation.pause()</title>
+<link rel="help" href="https://w3c.github.io/web-animations/#dom-animation-pause">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../testcommon.js"></script>
+<link rel="stylesheet" href="/resources/testharness.css">
+<body>
+<div id="log"></div>
+<script>
+"use strict";
+
+promise_test(function(t) {
+  var div = createDiv(t);
+  var animation = div.animate({}, 1000 * MS_PER_SEC);
+  var previousCurrentTime = animation.currentTime;
+
+  return animation.ready.then(waitForAnimationFrames(1)).then(function() {
+    assert_true(animation.currentTime >= previousCurrentTime,
+                'currentTime is initially increasing');
+    animation.pause();
+    return animation.ready;
+  }).then(function() {
+    previousCurrentTime = animation.currentTime;
+    return waitForAnimationFrames(1);
+  }).then(function() {
+    assert_equals(animation.currentTime, previousCurrentTime,
+                  'currentTime does not increase after calling pause()');
+  });
+}, 'pause() a running animation');
+
+promise_test(function(t) {
+  var div = createDiv(t);
+  var animation = div.animate({}, 1000 * MS_PER_SEC);
+
+  // Go to idle state then pause
+  animation.cancel();
+  animation.pause();
+
+  assert_equals(animation.currentTime, 0, 'currentTime is set to 0');
+  assert_equals(animation.startTime, null, 'startTime is not set');
+  assert_equals(animation.playState, 'pending', 'initially pause-pending');
+
+  // Check it still resolves as expected
+  return animation.ready.then(function() {
+    assert_equals(animation.playState, 'paused',
+                  'resolves to paused state asynchronously');
+    assert_equals(animation.currentTime, 0,
+                  'keeps the initially set currentTime');
+  });
+}, 'pause() from idle');
+
+promise_test(function(t) {
+  var div = createDiv(t);
+  var animation = div.animate({}, 1000 * MS_PER_SEC);
+  animation.cancel();
+  animation.playbackRate = -1;
+  animation.pause();
+
+  assert_equals(animation.currentTime, 1000 * MS_PER_SEC,
+                'currentTime is set to the effect end');
+
+  return animation.ready.then(function() {
+    assert_equals(animation.currentTime, 1000 * MS_PER_SEC,
+                  'keeps the initially set currentTime');
+  });
+}, 'pause() from idle with a negative playbackRate');
+
+test(function(t) {
+  var div = createDiv(t);
+  var animation = div.animate({}, {duration: 1000 * MS_PER_SEC,
+                                   iterations: Infinity});
+  animation.cancel();
+  animation.playbackRate = -1;
+
+  assert_throws('InvalidStateError',
+                function () { animation.pause(); },
+                'Expect InvalidStateError exception on calling pause() ' +
+                'from idle with a negative playbackRate and ' +
+                'infinite-duration animation');
+}, 'pause() from idle with a negative playbackRate and endless effect');
+
+promise_test(function(t) {
+  var div = createDiv(t);
+  var animation = div.animate({}, 1000 * MS_PER_SEC);
+  return animation.ready
+    .then(function(animation) {
+      animation.finish();
+      animation.pause();
+      return animation.ready;
+    }).then(function(animation) {
+      assert_equals(animation.currentTime, 1000 * MS_PER_SEC,
+                    'currentTime after pausing finished animation');
+    });
+}, 'pause() on a finished animation');
+
+</script>
+</body>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/web-animations/animation/ready.html
@@ -0,0 +1,97 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Animation.ready</title>
+<link rel="help" href="https://w3c.github.io/web-animations/#dom-animation-ready">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../testcommon.js"></script>
+<link rel="stylesheet" href="/resources/testharness.css">
+<body>
+<div id="log"></div>
+<script>
+"use strict";
+
+promise_test(function(t) {
+  var div = createDiv(t);
+  var animation = div.animate({}, 100 * MS_PER_SEC);
+  var originalReadyPromise = animation.ready;
+  var pauseReadyPromise;
+
+  return animation.ready.then(function() {
+    assert_equals(animation.ready, originalReadyPromise,
+                  'Ready promise is the same object when playing completes');
+    animation.pause();
+    assert_not_equals(animation.ready, originalReadyPromise,
+                      'A new ready promise is created when pausing');
+    pauseReadyPromise = animation.ready;
+    // Wait for the promise to fulfill since if we abort the pause the ready
+    // promise object is reused.
+    return animation.ready;
+  }).then(function() {
+    animation.play();
+    assert_not_equals(animation.ready, pauseReadyPromise,
+                      'A new ready promise is created when playing');
+  });
+}, 'A new ready promise is created when play()/pause() is called');
+
+promise_test(function(t) {
+  var div = createDiv(t);
+  var animation = div.animate({}, 100 * MS_PER_SEC);
+
+  return animation.ready.then(function() {
+    var promiseBeforeCallingPlay = animation.ready;
+    animation.play();
+    assert_equals(animation.ready, promiseBeforeCallingPlay,
+                  'Ready promise has same object identity after redundant call'
+                  + ' to play()');
+  });
+}, 'Redundant calls to play() do not generate new ready promise objects');
+
+promise_test(function(t) {
+  var div = createDiv(t);
+  var animation = div.animate({}, 100 * MS_PER_SEC);
+
+  return animation.ready.then(function(resolvedAnimation) {
+    assert_equals(resolvedAnimation, animation,
+                  'Object identity of Animation passed to Promise callback'
+                  + ' matches the Animation object owning the Promise');
+  });
+}, 'The ready promise is fulfilled with its Animation');
+
+promise_test(function(t) {
+  var div = createDiv(t);
+  var animation = div.animate({}, 100 * MS_PER_SEC);
+
+  var retPromise = animation.ready.then(function() {
+    assert_unreached('ready promise was fulfilled');
+  }).catch(function(err) {
+    assert_equals(err.name, 'AbortError',
+                  'ready promise is rejected with AbortError');
+  });
+
+  animation.cancel();
+
+  return retPromise;
+}, 'ready promise is rejected when a pause-pending animation is cancelled by'
+   + ' calling cancel()');
+
+promise_test(function(t) {
+  var div = createDiv(t);
+  var animation = div.animate({}, 100 * MS_PER_SEC);
+  return animation.ready.then(function() {
+    animation.pause();
+    // Set up listeners on pause-pending ready promise
+    var retPromise = animation.ready.then(function() {
+      assert_unreached('ready promise was fulfilled');
+    }).catch(function(err) {
+      assert_equals(err.name, 'AbortError',
+                    'ready promise is rejected with AbortError');
+    });
+    animation.cancel();
+    return retPromise;
+  });
+}, 'ready promise is rejected when a pause-pending animation is cancelled by'
+   + ' calling cancel()');
+
+</script>
+</body>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/web-animations/animation/reverse.html
@@ -0,0 +1,149 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Animation.reverse()</title>
+<link rel="help" href="https://w3c.github.io/web-animations/#dom-animation-reverse">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../testcommon.js"></script>
+<link rel="stylesheet" href="/resources/testharness.css">
+<body>
+<div id="log"></div>
+<script>
+"use strict";
+
+promise_test(function(t) {
+  var div = createDiv(t);
+  var animation = div.animate({}, {duration: 100 * MS_PER_SEC,
+                                   iterations: Infinity});
+
+  // Wait a frame because if currentTime is still 0 when we call
+  // reverse(), it will throw (per spec).
+  return animation.ready.then(waitForAnimationFrames(1)).then(function() {
+    assert_greater_than(animation.currentTime, 0,
+      'currentTime expected to be greater than 0, one frame after starting');
+    var previousPlaybackRate = animation.playbackRate;
+    animation.reverse();
+    assert_equals(animation.playbackRate, -previousPlaybackRate,
+      'playbackRate should be inverted');
+  });
+}, 'reverse() inverts playbackRate');
+
+promise_test(function(t) {
+  var div = createDiv(t);
+  var animation = div.animate({}, {duration: 100 * MS_PER_SEC,
+                                   iterations: Infinity});
+  animation.currentTime = 50 * MS_PER_SEC;
+  animation.pause();
+
+  return animation.ready.then(function() {
+    animation.reverse();
+    return animation.ready;
+  }).then(function() {
+    assert_equals(animation.playState, 'running',
+      'Animation.playState should be "running" after reverse()');
+  });
+}, 'reverse() starts to play when pausing animation');
+
+test(function(t) {
+  var div = createDiv(t);
+  var animation = div.animate({}, 100 * MS_PER_SEC);
+  animation.currentTime = 50 * MS_PER_SEC;
+  animation.reverse();
+
+  assert_equals(animation.currentTime, 50 * MS_PER_SEC,
+    'reverse() should not change the currentTime ' +
+    'if the currentTime is in the middle of animation duration');
+}, 'reverse() maintains the same currentTime');
+
+test(function(t) {
+  var div = createDiv(t);
+  var animation = div.animate({}, 100 * MS_PER_SEC);
+  animation.currentTime = 200 * MS_PER_SEC;
+  animation.reverse();
+
+  assert_equals(animation.currentTime, 100 * MS_PER_SEC,
+    'reverse() should start playing from the animation effect end ' +
+    'if the playbackRate > 0 and the currentTime > effect end');
+}, 'reverse() when playbackRate > 0 and currentTime > effect end');
+
+test(function(t) {
+  var div = createDiv(t);
+  var animation = div.animate({}, 100 * MS_PER_SEC);
+
+  animation.currentTime = -200 * MS_PER_SEC;
+  animation.reverse();
+
+  assert_equals(animation.currentTime, 100 * MS_PER_SEC,
+    'reverse() should start playing from the animation effect end ' +
+    'if the playbackRate > 0 and the currentTime < 0');
+}, 'reverse() when playbackRate > 0 and currentTime < 0');
+
+test(function(t) {
+  var div = createDiv(t);
+  var animation = div.animate({}, 100 * MS_PER_SEC);
+  animation.playbackRate = -1;
+  animation.currentTime = -200 * MS_PER_SEC;
+  animation.reverse();
+
+  assert_equals(animation.currentTime, 0,
+    'reverse() should start playing from the start of animation time ' +
+    'if the playbackRate < 0 and the currentTime < 0');
+}, 'reverse() when playbackRate < 0 and currentTime < 0');
+
+test(function(t) {
+  var div = createDiv(t);
+  var animation = div.animate({}, 100 * MS_PER_SEC);
+  animation.playbackRate = -1;
+  animation.currentTime = 200 * MS_PER_SEC;
+  animation.reverse();
+
+  assert_equals(animation.currentTime, 0,
+    'reverse() should start playing from the start of animation time ' +
+    'if the playbackRate < 0 and the currentTime > effect end');
+}, 'reverse() when playbackRate < 0 and currentTime > effect end');
+
+test(function(t) {
+  var div = createDiv(t);
+  var animation = div.animate({}, {duration: 100 * MS_PER_SEC,
+                                   iterations: Infinity});
+  animation.currentTime = -200 * MS_PER_SEC;
+
+  assert_throws('InvalidStateError',
+    function () { animation.reverse(); },
+    'reverse() should throw InvalidStateError ' +
+    'if the playbackRate > 0 and the currentTime < 0 ' +
+    'and the target effect is positive infinity');
+}, 'reverse() when playbackRate > 0 and currentTime < 0 ' +
+   'and the target effect end is positive infinity');
+
+test(function(t) {
+  var div = createDiv(t);
+  var animation = div.animate({}, {duration: 100 * MS_PER_SEC,
+                                   iterations: Infinity});
+  animation.playbackRate = -1;
+  animation.currentTime = -200 * MS_PER_SEC;
+  animation.reverse();
+
+  assert_equals(animation.currentTime, 0,
+    'reverse() should start playing from the start of animation time ' +
+    'if the playbackRate < 0 and the currentTime < 0 ' +
+    'and the target effect is positive infinity');
+}, 'reverse() when playbackRate < 0 and currentTime < 0 ' +
+   'and the target effect end is positive infinity');
+
+test(function(t) {
+  var div = createDiv(t);
+  var animation = div.animate({}, 100 * MS_PER_SEC);
+  animation.playbackRate = 0;
+  animation.currentTime = 50 * MS_PER_SEC;
+  animation.reverse();
+
+  assert_equals(animation.playbackRate, 0,
+    'reverse() should preserve playbackRate if the playbackRate == 0');
+  assert_equals(animation.currentTime, 50 * MS_PER_SEC,
+    'reverse() should not affect the currentTime if the playbackRate == 0');
+  t.done();
+}, 'reverse() when playbackRate == 0');
+
+</script>
+</body>