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 317623 0e54229af7f4ab003fb0572926ee9a2e283e6d35
parent 317622 b72de5c5ac3363c5fe7dc94c74a13a1152720100
child 317624 545b4ff9a8e022658fc21ccf4fbad47ac30c97ee
push id9480
push userjlund@mozilla.com
push dateMon, 25 Apr 2016 17:12:58 +0000
treeherdermozilla-aurora@0d6a91c76a9e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbirtles
bugs1260084
milestone48.0a1
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>