Bug 1406285 - Part 17: Add tests. r?gl draft
authorDaisuke Akatsuka <dakatsuka@mozilla.com>
Thu, 18 Jan 2018 14:17:55 +0900
changeset 721946 534acab8ae4949663ea00be93bf2c0754b956b31
parent 721945 3dd820b95fab12337cce197f1de6d3df7e259dbc
child 721947 e5b844e7fd8225c47e1e1fa403a9d3f6dcba6f7d
child 721961 70ce2d4c5a4f682b1ef82e1f03249dcae841a364
push id96003
push userbmo:dakatsuka@mozilla.com
push dateThu, 18 Jan 2018 05:23:36 +0000
reviewersgl
bugs1406285
milestone59.0a1
Bug 1406285 - Part 17: Add tests. r?gl MozReview-Commit-ID: HjbxUZ4B4lE
devtools/client/inspector/animation/components/AnimationItem.js
devtools/client/inspector/animation/components/graph/SummaryGraph.js
devtools/client/inspector/animation/components/graph/SummaryGraphPath.js
devtools/client/inspector/animation/test/browser.ini
devtools/client/inspector/animation/test/browser_animation_AnimationTarget.js
devtools/client/inspector/animation/test/browser_animation_SummaryGraph_AnimationName.js
devtools/client/inspector/animation/test/browser_animation_SummaryGraph_ComputedTimingPath.js
devtools/client/inspector/animation/test/browser_animation_SummaryGraph_DelaySign.js
devtools/client/inspector/animation/test/browser_animation_SummaryGraph_EffectTimingPath.js
devtools/client/inspector/animation/test/browser_animation_SummaryGraph_EndDelaySign.js
devtools/client/inspector/animation/test/browser_animation_SummaryGraph_NegativeDelayPath.js
devtools/client/inspector/animation/test/browser_animation_SummaryGraph_NegativeEndDelayPath.js
devtools/client/inspector/animation/test/browser_animation_SummaryGraph_compositor.js
devtools/client/inspector/animation/test/browser_animation_SummaryGraph_tooltip.js
devtools/client/inspector/animation/test/browser_animation_animation_list_exists.js
devtools/client/inspector/animation/test/doc_multi_timings.html
devtools/client/inspector/animation/test/head.js
--- a/devtools/client/inspector/animation/components/AnimationItem.js
+++ b/devtools/client/inspector/animation/components/AnimationItem.js
@@ -51,16 +51,17 @@ class AnimationItem extends PureComponen
           onHideBoxModelHighlighter,
           onShowBoxModelHighlighterForNode,
           setSelectedNode,
         }
       ),
       SummaryGraph(
         {
           animation,
+          emitEventForTest,
           getAnimatedPropertyMap,
           simulateAnimation,
           timeScale,
         }
       )
     );
   }
 }
--- a/devtools/client/inspector/animation/components/graph/SummaryGraph.js
+++ b/devtools/client/inspector/animation/components/graph/SummaryGraph.js
@@ -14,16 +14,17 @@ const EndDelaySign = createFactory(requi
 const SummaryGraphPath = createFactory(require("./SummaryGraphPath"));
 
 const { getFormatStr, getStr, numberWithDecimals } = require("../../utils/l10n");
 
 class SummaryGraph extends PureComponent {
   static get propTypes() {
     return {
       animation: PropTypes.object.isRequired,
+      emitEventForTest: PropTypes.func.isRequired,
       getAnimatedPropertyMap: PropTypes.func.isRequired,
       simulateAnimation: PropTypes.func.isRequired,
       timeScale: PropTypes.object.isRequired,
     };
   }
 
   getTitleText(state) {
     const getTime =
@@ -119,30 +120,32 @@ class SummaryGraph extends PureComponent
     }
 
     return text;
   }
 
   render() {
     const {
       animation,
+      emitEventForTest,
       getAnimatedPropertyMap,
       simulateAnimation,
       timeScale,
     } = this.props;
 
     return dom.div(
       {
         className: "animation-summary-graph" +
                    (animation.state.isRunningOnCompositor ? " compositor" : ""),
         title: this.getTitleText(animation.state),
       },
       SummaryGraphPath(
         {
           animation,
+          emitEventForTest,
           getAnimatedPropertyMap,
           simulateAnimation,
           timeScale,
         }
       ),
       animation.state.delay ?
         DelaySign(
           {
--- a/devtools/client/inspector/animation/components/graph/SummaryGraphPath.js
+++ b/devtools/client/inspector/animation/components/graph/SummaryGraphPath.js
@@ -17,16 +17,17 @@ const { DEFAULT_GRAPH_HEIGHT } = require
 
 // Minimum opacity for semitransparent fill color for keyframes's easing graph.
 const MIN_KEYFRAMES_EASING_OPACITY = 0.5;
 
 class SummaryGraphPath extends PureComponent {
   static get propTypes() {
     return {
       animation: PropTypes.object.isRequired,
+      emitEventForTest: PropTypes.func.isRequired,
       getAnimatedPropertyMap: PropTypes.object.isRequired,
       simulateAnimation: PropTypes.func.isRequired,
       timeScale: PropTypes.object.isRequired,
     };
   }
 
   constructor(props) {
     super(props);
@@ -137,28 +138,31 @@ class SummaryGraphPath extends PureCompo
       }
     }
 
     return true;
   }
 
   async updateState(animation) {
     const {
+      emitEventForTest,
       getAnimatedPropertyMap,
       timeScale,
     } = this.props;
 
     const animatedPropertyMap = await getAnimatedPropertyMap(animation);
     const keyframesList = this.getOffsetAndEasingOnlyKeyframes(animatedPropertyMap);
 
     const thisEl = ReactDOM.findDOMNode(this);
     const totalDuration = this.getTotalDuration(animation, timeScale);
     const durationPerPixel = totalDuration / thisEl.parentNode.clientWidth;
 
     this.setState({ durationPerPixel, keyframesList });
+
+    emitEventForTest("animation-summary-graph-rendered");
   }
 
   render() {
     const { durationPerPixel, keyframesList } = this.state;
 
     if (!durationPerPixel) {
       return dom.svg();
     }
--- a/devtools/client/inspector/animation/test/browser.ini
+++ b/devtools/client/inspector/animation/test/browser.ini
@@ -1,17 +1,27 @@
 [DEFAULT]
 tags = devtools
 subsuite = devtools
 support-files =
+  doc_multi_timings.html
   doc_simple_animation.html
   head.js
   !/devtools/client/framework/test/shared-head.js
   !/devtools/client/inspector/test/head.js
   !/devtools/client/inspector/test/shared-head.js
   !/devtools/client/shared/test/test-actor-registry.js
   !/devtools/client/shared/test/test-actor.js
 
 [browser_animation_animation_list_exists.js]
 [browser_animation_animation_list_time_tick.js]
 [browser_animation_AnimationTarget.js]
 [browser_animation_empty_on_invalid_nodes.js]
 [browser_animation_inspector_exists.js]
+[browser_animation_SummaryGraph_AnimationName.js]
+[browser_animation_SummaryGraph_compositor.js]
+[browser_animation_SummaryGraph_ComputedTimingPath.js]
+[browser_animation_SummaryGraph_DelaySign.js]
+[browser_animation_SummaryGraph_EndDelaySign.js]
+[browser_animation_SummaryGraph_EffectTimingPath.js]
+[browser_animation_SummaryGraph_NegativeDelayPath.js]
+[browser_animation_SummaryGraph_NegativeEndDelayPath.js]
+[browser_animation_SummaryGraph_tooltip.js]
--- a/devtools/client/inspector/animation/test/browser_animation_AnimationTarget.js
+++ b/devtools/client/inspector/animation/test/browser_animation_AnimationTarget.js
@@ -1,14 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
-// Test existance and content of animation target.
+// Test for following AnimationTarget component works.
+// * element existance
+// * number of elements
+// * content of element
 
 add_task(async function () {
   await addTab(URL_ROOT + "doc_simple_animation.html");
   const { animationInspector, inspector, panel } = await openAnimationInspector();
 
   info("Checking the animation target elements existance");
   const animationItemEls = panel.querySelectorAll(".animation-list .animation-item");
   is(animationItemEls.length, animationInspector.animations.length,
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/animation/test/browser_animation_SummaryGraph_AnimationName.js
@@ -0,0 +1,59 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for following AnimationName component works.
+// * element existance
+// * name text
+
+const TEST_CASES = [
+  {
+    targetClassName: "cssanimation-normal",
+    expectedLabel: "cssanimation",
+  },
+  {
+    targetClassName: "cssanimation-linear",
+    expectedLabel: "cssanimation",
+  },
+  {
+    targetClassName: "delay-positive",
+    expectedLabel: "test-delay-animation",
+  },
+  {
+    targetClassName: "delay-negative",
+    expectedLabel: "test-negative-delay-animation",
+  },
+  {
+    targetClassName: "easing-step",
+  },
+];
+
+add_task(async function () {
+  await addTab(URL_ROOT + "doc_multi_timings.html");
+
+  const { panel } = await openAnimationInspector();
+
+  for (const testCase of TEST_CASES) {
+    const {
+      expectedLabel,
+      targetClassName,
+    } = testCase;
+
+    const animationItemEl =
+      findAnimationItemElementsByTargetClassName(panel, targetClassName);
+
+    info(`Checking animation name element existance for ${ targetClassName }`);
+    const animationNameEl = animationItemEl.querySelector(".animation-name");
+
+    if (expectedLabel) {
+      ok(animationNameEl,
+         "The animation name element should be in animation item element");
+      is(animationNameEl.textContent, expectedLabel,
+         `The animation name should be ${ expectedLabel }`);
+    } else {
+      ok(!animationNameEl,
+         "The animation name element should not be in animation item element");
+    }
+  }
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/animation/test/browser_animation_SummaryGraph_ComputedTimingPath.js
@@ -0,0 +1,466 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for following ComputedTimingPath component works.
+// * element existance
+// * iterations: path, count
+// * delay: path
+// * fill: path
+// * endDelay: path
+
+const TEST_CASES = [
+  {
+    targetClassName: "cssanimation-normal",
+    expectedIterationPathList: [
+      [
+        { x: 0, y: 0 },
+        { x: 25000, y: 40.851 },
+        { x: 50000, y: 80.24},
+        { x: 75000, y: 96.05 },
+        { x: 100000, y: 100 },
+        { x: 100000, y: 0 },
+      ]
+    ],
+  },
+  {
+    targetClassName: "cssanimation-linear",
+    expectedIterationPathList: [
+      [
+        { x: 0, y: 0 },
+        { x: 25000, y: 25 },
+        { x: 50000, y: 50 },
+        { x: 75000, y: 75 },
+        { x: 100000, y: 100 },
+        { x: 100000, y: 0 },
+      ]
+    ],
+  },
+  {
+    targetClassName: "delay-positive",
+    expectedDelayPath: [
+      { x: 0, y: 0 },
+      { x: 50000, y: 0 },
+    ],
+    expectedIterationPathList: [
+      [
+        { x: 50000, y: 0 },
+        { x: 75000, y: 25 },
+        { x: 100000, y: 50 },
+        { x: 125000, y: 75 },
+        { x: 150000, y: 100 },
+        { x: 150000, y: 0 },
+      ]
+    ],
+  },
+  {
+    targetClassName: "delay-negative",
+    expectedIterationPathList: [
+      [
+        { x: 0, y: 0 },
+        { x: 0, y: 50 },
+        { x: 25000, y: 75 },
+        { x: 50000, y: 100 },
+        { x: 50000, y: 0 },
+      ]
+    ],
+  },
+  {
+    targetClassName: "easing-step",
+    expectedIterationPathList: [
+      [
+        { x: 0, y: 0 },
+        { x: 49999, y: 0 },
+        { x: 50000, y: 50 },
+        { x: 99999, y: 50 },
+        { x: 100000, y: 0 },
+      ]
+    ],
+  },
+  {
+    targetClassName: "enddelay-positive",
+    expectedIterationPathList: [
+      [
+        { x: 0, y: 0 },
+        { x: 25000, y: 25 },
+        { x: 50000, y: 50 },
+        { x: 75000, y: 75 },
+        { x: 100000, y: 100 },
+        { x: 100000, y: 0 },
+      ]
+    ],
+    expectedEndDelayPath: [
+      { x: 100000, y: 0 },
+      { x: 150000, y: 0 },
+    ],
+  },
+  {
+    targetClassName: "enddelay-negative",
+    expectedIterationPathList: [
+      [
+        { x: 0, y: 0 },
+        { x: 25000, y: 25 },
+        { x: 50000, y: 50 },
+        { x: 50000, y: 0 },
+      ]
+    ],
+  },
+  {
+    targetClassName: "enddelay-with-fill-forwards",
+    expectedIterationPathList: [
+      [
+        { x: 0, y: 0 },
+        { x: 25000, y: 25 },
+        { x: 50000, y: 50 },
+        { x: 75000, y: 75 },
+        { x: 100000, y: 100 },
+        { x: 100000, y: 0 },
+      ]
+    ],
+    expectedEndDelayPath: [
+      { x: 100000, y: 0 },
+      { x: 100000, y: 100 },
+      { x: 150000, y: 100 },
+      { x: 150000, y: 0 },
+    ],
+    expectedForwardsPath: [
+      { x: 150000, y: 0 },
+      { x: 150000, y: 100 },
+      { x: 200000, y: 100 },
+      { x: 200000, y: 0 },
+    ],
+  },
+  {
+    targetClassName: "enddelay-with-iterations-infinity",
+    expectedIterationPathList: [
+      [
+        { x: 0, y: 0 },
+        { x: 25000, y: 25 },
+        { x: 50000, y: 50 },
+        { x: 75000, y: 75 },
+        { x: 100000, y: 100 },
+        { x: 100000, y: 0 },
+      ],
+      [
+        { x: 100000, y: 0 },
+        { x: 125000, y: 25 },
+        { x: 150000, y: 50 },
+        { x: 175000, y: 75 },
+        { x: 200000, y: 100 },
+        { x: 200000, y: 0 },
+      ]
+    ],
+    isInfinity: true,
+  },
+  {
+    targetClassName: "direction-alternate-with-iterations-infinity",
+    expectedIterationPathList: [
+      [
+        { x: 0, y: 0 },
+        { x: 25000, y: 25 },
+        { x: 50000, y: 50 },
+        { x: 75000, y: 75 },
+        { x: 100000, y: 100 },
+        { x: 100000, y: 0 },
+      ],
+      [
+        { x: 100000, y: 0 },
+        { x: 100000, y: 100 },
+        { x: 125000, y: 75 },
+        { x: 150000, y: 50 },
+        { x: 175000, y: 25 },
+        { x: 200000, y: 0 },
+      ]
+    ],
+    isInfinity: true,
+  },
+  {
+    targetClassName: "direction-alternate-reverse-with-iterations-infinity",
+    expectedIterationPathList: [
+      [
+        { x: 0, y: 0 },
+        { x: 0, y: 100 },
+        { x: 25000, y: 75 },
+        { x: 50000, y: 50 },
+        { x: 75000, y: 25 },
+        { x: 100000, y: 0 },
+      ],
+      [
+        { x: 100000, y: 0 },
+        { x: 125000, y: 25 },
+        { x: 150000, y: 50 },
+        { x: 175000, y: 75 },
+        { x: 200000, y: 100 },
+        { x: 200000, y: 0 },
+      ]
+    ],
+    isInfinity: true,
+  },
+  {
+    targetClassName: "direction-reverse-with-iterations-infinity",
+    expectedIterationPathList: [
+      [
+        { x: 0, y: 0 },
+        { x: 0, y: 100 },
+        { x: 25000, y: 75 },
+        { x: 50000, y: 50 },
+        { x: 75000, y: 25 },
+        { x: 100000, y: 0 },
+      ],
+      [
+        { x: 100000, y: 0 },
+        { x: 100000, y: 100 },
+        { x: 125000, y: 75 },
+        { x: 150000, y: 50 },
+        { x: 175000, y: 25 },
+        { x: 200000, y: 0 },
+      ]
+    ],
+    isInfinity: true,
+  },
+  {
+    targetClassName: "fill-backwards",
+    expectedIterationPathList: [
+      [
+        { x: 0, y: 0 },
+        { x: 25000, y: 25 },
+        { x: 50000, y: 50 },
+        { x: 75000, y: 75 },
+        { x: 100000, y: 100 },
+        { x: 100000, y: 0 },
+      ]
+    ],
+  },
+  {
+    targetClassName: "fill-backwards-with-delay-iterationstart",
+    expectedDelayPath: [
+      { x: 0, y: 0 },
+      { x: 0, y: 50 },
+      { x: 50000, y: 50 },
+      { x: 50000, y: 0 },
+    ],
+    expectedIterationPathList: [
+      [
+        { x: 50000, y: 50 },
+        { x: 75000, y: 75 },
+        { x: 100000, y: 100 },
+        { x: 100000, y: 0 },
+      ],
+      [
+        { x: 100000, y: 0 },
+        { x: 125000, y: 25 },
+        { x: 150000, y: 50 },
+        { x: 150000, y: 0 },
+      ]
+    ],
+  },
+  {
+    targetClassName: "fill-both",
+    expectedIterationPathList: [
+      [
+        { x: 0, y: 0 },
+        { x: 25000, y: 25 },
+        { x: 50000, y: 50 },
+        { x: 75000, y: 75 },
+        { x: 100000, y: 100 },
+        { x: 100000, y: 0 },
+      ]
+    ],
+    expectedForwardsPath: [
+      { x: 100000, y: 0 },
+      { x: 100000, y: 100 },
+      { x: 200000, y: 100 },
+      { x: 200000, y: 0 },
+    ],
+  },
+  {
+    targetClassName: "fill-both-width-delay-iterationstart",
+    expectedDelayPath: [
+      { x: 0, y: 0 },
+      { x: 0, y: 50 },
+      { x: 50000, y: 50 },
+      { x: 50000, y: 0 },
+    ],
+    expectedIterationPathList: [
+      [
+        { x: 50000, y: 50 },
+        { x: 75000, y: 75 },
+        { x: 100000, y: 100 },
+        { x: 100000, y: 0 },
+      ],
+      [
+        { x: 100000, y: 0 },
+        { x: 125000, y: 25 },
+        { x: 150000, y: 50 },
+        { x: 150000, y: 0 },
+      ]
+    ],
+    expectedForwardsPath: [
+      { x: 150000, y: 0 },
+      { x: 150000, y: 50 },
+    ],
+  },
+  {
+    targetClassName: "fill-forwards",
+    expectedIterationPathList: [
+      [
+        { x: 0, y: 0 },
+        { x: 25000, y: 25 },
+        { x: 50000, y: 50 },
+        { x: 75000, y: 75 },
+        { x: 100000, y: 100 },
+        { x: 100000, y: 0 },
+      ]
+    ],
+    expectedForwardsPath: [
+      { x: 100000, y: 0 },
+      { x: 100000, y: 100 },
+      { x: 200000, y: 100 },
+      { x: 200000, y: 0 },
+    ],
+  },
+  {
+    targetClassName: "iterationstart",
+    expectedIterationPathList: [
+      [
+        { x: 0, y: 50 },
+        { x: 25000, y: 75 },
+        { x: 50000, y: 100 },
+        { x: 50000, y: 0 },
+      ],
+      [
+        { x: 50000, y: 0 },
+        { x: 75000, y: 25 },
+        { x: 100000, y: 50 },
+        { x: 100000, y: 0 },
+      ]
+    ],
+  },
+  {
+    targetClassName: "no-compositor",
+    expectedIterationPathList: [
+      [
+        { x: 0, y: 0 },
+        { x: 25000, y: 25 },
+        { x: 50000, y: 50 },
+        { x: 75000, y: 75 },
+        { x: 100000, y: 100 },
+        { x: 100000, y: 0 },
+      ]
+    ],
+  },
+  {
+    targetClassName: "keyframes-easing-step",
+    expectedIterationPathList: [
+      [
+        { x: 0, y: 0 },
+        { x: 49999, y: 0 },
+        { x: 50000, y: 50 },
+        { x: 99999, y: 50 },
+        { x: 100000, y: 0 },
+      ]
+    ],
+  },
+  {
+    targetClassName: "narrow-keyframes",
+    expectedIterationPathList: [
+      [
+        { x: 0, y: 0 },
+        { x: 10000, y: 10 },
+        { x: 11000, y: 10 },
+        { x: 11500, y: 10 },
+        { x: 12999, y: 10 },
+        { x: 13000, y: 13 },
+        { x: 13500, y: 13.5 },
+      ]
+    ],
+  },
+  {
+    targetClassName: "duplicate-offsets",
+    expectedIterationPathList: [
+      [
+        { x: 0, y: 0 },
+        { x: 25000, y: 25 },
+        { x: 50000, y: 50 },
+        { x: 99999, y: 50 },
+      ]
+    ],
+  },
+];
+
+add_task(async function () {
+  await addTab(URL_ROOT + "doc_multi_timings.html");
+
+  const { panel } = await openAnimationInspector();
+
+  for (const testCase of TEST_CASES) {
+    const {
+      expectedDelayPath,
+      expectedEndDelayPath,
+      expectedForwardsPath,
+      expectedIterationPathList,
+      isInfinity,
+      targetClassName,
+    } = testCase;
+
+    const animationItemEl =
+      findAnimationItemElementsByTargetClassName(panel, targetClassName);
+
+    info(`Checking computed timing path existance for ${ targetClassName }`);
+    const computedTimingPathEl =
+      animationItemEl.querySelector(".animation-computed-timing-path");
+    ok(computedTimingPathEl,
+       "The computed timing path element should be in each animation item element");
+
+    info(`Checking delay path for ${ targetClassName }`);
+    const delayPathEl = computedTimingPathEl.querySelector(".animation-delay-path");
+
+    if (expectedDelayPath) {
+      ok(delayPathEl, "delay path should be existance");
+      assertPathSegments(delayPathEl, true, expectedDelayPath);
+    } else {
+      ok(!delayPathEl, "delay path should not be existance");
+    }
+
+    info(`Checking iteration path list for ${ targetClassName }`);
+    const iterationPathEls =
+      computedTimingPathEl.querySelectorAll(".animation-iteration-path");
+    is(iterationPathEls.length, expectedIterationPathList.length,
+       `Number of iteration path should be ${ expectedIterationPathList.length }`);
+
+    for (const [j, iterationPathEl] of iterationPathEls.entries()) {
+      assertPathSegments(iterationPathEl, true, expectedIterationPathList[j]);
+
+      info(`Checking infinity ${ targetClassName }`);
+      if (isInfinity && j >= 1) {
+        ok(iterationPathEl.classList.contains("infinity"),
+           "iteration path should have 'infinity' class");
+      } else {
+        ok(!iterationPathEl.classList.contains("infinity"),
+           "iteration path should not have 'infinity' class");
+      }
+    }
+
+    info(`Checking endDelay path for ${ targetClassName }`);
+    const endDelayPathEl = computedTimingPathEl.querySelector(".animation-enddelay-path");
+
+    if (expectedEndDelayPath) {
+      ok(endDelayPathEl, "endDelay path should be existance");
+      assertPathSegments(endDelayPathEl, true, expectedEndDelayPath);
+    } else {
+      ok(!endDelayPathEl, "endDelay path should not be existance");
+    }
+
+    info(`Checking forwards fill path for ${ targetClassName }`);
+    const forwardsPathEl =
+      computedTimingPathEl.querySelector(".animation-fill-forwards-path");
+
+    if (expectedForwardsPath) {
+      ok(forwardsPathEl, "forwards path should be existance");
+      assertPathSegments(forwardsPathEl, true, expectedForwardsPath);
+    } else {
+      ok(!forwardsPathEl, "forwards path should not be existance");
+    }
+  }
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/animation/test/browser_animation_SummaryGraph_DelaySign.js
@@ -0,0 +1,89 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for following DelaySign component works.
+// * element existance
+// * left position
+// * width
+// * additinal class
+
+const TEST_CASES = [
+  {
+    targetClassName: "delay-positive",
+    expectedResult: {
+      left: "25%",
+      width: "25%",
+    },
+  },
+  {
+    targetClassName: "delay-negative",
+    expectedResult: {
+      additionalClass: "negative",
+      left: "0%",
+      width: "25%",
+    },
+  },
+  {
+    targetClassName: "fill-backwards-with-delay-iterationstart",
+    expectedResult: {
+      additionalClass: "fill",
+      left: "25%",
+      width: "25%",
+    },
+  },
+  {
+    targetClassName: "fill-both",
+  },
+  {
+    targetClassName: "fill-both-width-delay-iterationstart",
+    expectedResult: {
+      additionalClass: "fill",
+      left: "25%",
+      width: "25%",
+    },
+  },
+  {
+    targetClassName: "keyframes-easing-step",
+  },
+];
+
+add_task(async function () {
+  await addTab(URL_ROOT + "doc_multi_timings.html");
+
+  const { panel } = await openAnimationInspector();
+
+  for (const testCase of TEST_CASES) {
+    const {
+      expectedResult,
+      targetClassName,
+    } = testCase;
+
+    const animationItemEl =
+      findAnimationItemElementsByTargetClassName(panel, targetClassName);
+
+    info(`Checking delay sign existance for ${ targetClassName }`);
+    const delaySignEl = animationItemEl.querySelector(".animation-delay-sign");
+
+    if (expectedResult) {
+      ok(delaySignEl, "The delay sign element should be in animation item element");
+
+      is(delaySignEl.style.left, expectedResult.left,
+         `Left position should be ${ expectedResult.left }`);
+      is(delaySignEl.style.width, expectedResult.width,
+         `Width should be ${ expectedResult.width }`);
+
+      if (expectedResult.additionalClass) {
+        ok(delaySignEl.classList.contains(expectedResult.additionalClass),
+           `delay sign element should have ${ expectedResult.additionalClass } class`);
+      } else {
+        ok(!delaySignEl.classList.contains(expectedResult.additionalClass),
+           "delay sign element should not have " +
+           `${ expectedResult.additionalClass } class`);
+      }
+    } else {
+      ok(!delaySignEl, "The delay sign element should not be in animation item element");
+    }
+  }
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/animation/test/browser_animation_SummaryGraph_EffectTimingPath.js
@@ -0,0 +1,60 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for following EffectTimingPath component works.
+// * element existance
+// * path
+
+const TEST_CASES = [
+  {
+    targetClassName: "cssanimation-linear",
+  },
+  {
+    targetClassName: "delay-negative",
+  },
+  {
+    targetClassName: "easing-step",
+    expectedPath: [
+      { x: 0, y: 0 },
+      { x: 49900, y: 0 },
+      { x: 50000, y: 50 },
+      { x: 99999, y: 50 },
+      { x: 100000, y: 0 },
+    ],
+  },
+  {
+    targetClassName: "keyframes-easing-step",
+  },
+];
+
+add_task(async function () {
+  await addTab(URL_ROOT + "doc_multi_timings.html");
+
+  const { panel } = await openAnimationInspector();
+
+  for (const testCase of TEST_CASES) {
+    const {
+      expectedPath,
+      targetClassName,
+    } = testCase;
+
+    const animationItemEl =
+      findAnimationItemElementsByTargetClassName(panel, targetClassName);
+
+    info(`Checking effect timing path existance for ${ targetClassName }`);
+    const effectTimingPathEl =
+      animationItemEl.querySelector(".animation-effect-timing-path");
+
+    if (expectedPath) {
+      ok(effectTimingPathEl,
+         "The effect timing path element should be in animation item element");
+      const pathEl = effectTimingPathEl.querySelector(".animation-iteration-path");
+      assertPathSegments(pathEl, false, expectedPath);
+    } else {
+      ok(!effectTimingPathEl,
+         "The effect timing path element should not be in animation item element");
+    }
+  }
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/animation/test/browser_animation_SummaryGraph_EndDelaySign.js
@@ -0,0 +1,82 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for following EndDelaySign component works.
+// * element existance
+// * left position
+// * width
+// * additinal class
+
+const TEST_CASES = [
+  {
+    targetClassName: "enddelay-positive",
+    expectedResult: {
+      left: "75%",
+      width: "25%",
+    },
+  },
+  {
+    targetClassName: "enddelay-negative",
+    expectedResult: {
+      additionalClass: "negative",
+      left: "50%",
+      width: "25%",
+    },
+  },
+  {
+    targetClassName: "enddelay-with-fill-forwards",
+    expectedResult: {
+      additionalClass: "fill",
+      left: "75%",
+      width: "25%",
+    },
+  },
+  {
+    targetClassName: "enddelay-with-iterations-infinity",
+  },
+  {
+    targetClassName: "keyframes-easing-step",
+  },
+];
+
+add_task(async function () {
+  await addTab(URL_ROOT + "doc_multi_timings.html");
+
+  const { panel } = await openAnimationInspector();
+
+  for (const testCase of TEST_CASES) {
+    const {
+      expectedResult,
+      targetClassName,
+    } = testCase;
+
+    const animationItemEl =
+      findAnimationItemElementsByTargetClassName(panel, targetClassName);
+
+    info(`Checking endDelay sign existance for ${ targetClassName }`);
+    const endDelaySignEl = animationItemEl.querySelector(".animation-end-delay-sign");
+
+    if (expectedResult) {
+      ok(endDelaySignEl, "The endDelay sign element should be in animation item element");
+
+      is(endDelaySignEl.style.left, expectedResult.left,
+         `Left position should be ${ expectedResult.left }`);
+      is(endDelaySignEl.style.width, expectedResult.width,
+         `Width should be ${ expectedResult.width }`);
+
+      if (expectedResult.additionalClass) {
+        ok(endDelaySignEl.classList.contains(expectedResult.additionalClass),
+           `endDelay sign element should have ${ expectedResult.additionalClass } class`);
+      } else {
+        ok(!endDelaySignEl.classList.contains(expectedResult.additionalClass),
+           "endDelay sign element should not have " +
+           `${ expectedResult.additionalClass } class`);
+      }
+    } else {
+      ok(!endDelaySignEl,
+         "The endDelay sign element should not be in animation item element");
+    }
+  }
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/animation/test/browser_animation_SummaryGraph_NegativeDelayPath.js
@@ -0,0 +1,53 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for following NegativeDelayPath component works.
+// * element existance
+// * path
+
+const TEST_CASES = [
+  {
+    targetClassName: "delay-positive",
+  },
+  {
+    targetClassName: "delay-negative",
+    expectedPath: [
+      { x: -50000, y: 0 },
+      { x: -25000, y: 25 },
+      { x: 0, y: 50 },
+      { x: 0, y: 0 },
+    ],
+  },
+];
+
+add_task(async function () {
+  await addTab(URL_ROOT + "doc_multi_timings.html");
+
+  const { panel } = await openAnimationInspector();
+
+  for (const testCase of TEST_CASES) {
+    const {
+      expectedPath,
+      targetClassName,
+    } = testCase;
+
+    const animationItemEl =
+      findAnimationItemElementsByTargetClassName(panel, targetClassName);
+
+    info(`Checking negative delay path existence for ${ targetClassName }`);
+    const negativeDelayPathEl =
+      animationItemEl.querySelector(".animation-negative-delay-path");
+
+    if (expectedPath) {
+      ok(negativeDelayPathEl,
+         "The negative delay path element should be in animation item element");
+      const pathEl = negativeDelayPathEl.querySelector("path");
+      assertPathSegments(pathEl, true, expectedPath);
+    } else {
+      ok(!negativeDelayPathEl,
+         "The negative delay path element should not be in animation item element");
+    }
+  }
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/animation/test/browser_animation_SummaryGraph_NegativeEndDelayPath.js
@@ -0,0 +1,54 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for following NegativeEndDelayPath component works.
+// * element existance
+// * path
+
+const TEST_CASES = [
+  {
+    targetClassName: "enddelay-positive",
+  },
+  {
+    targetClassName: "enddelay-negative",
+    expectedPath: [
+      { x: 50000, y: 0 },
+      { x: 50000, y: 50 },
+      { x: 75000, y: 75 },
+      { x: 100000, y: 100 },
+      { x: 100000, y: 0 },
+    ],
+  },
+];
+
+add_task(async function () {
+  await addTab(URL_ROOT + "doc_multi_timings.html");
+
+  const { panel } = await openAnimationInspector();
+
+  for (const testCase of TEST_CASES) {
+    const {
+      expectedPath,
+      targetClassName,
+    } = testCase;
+
+    const animationItemEl =
+      findAnimationItemElementsByTargetClassName(panel, targetClassName);
+
+    info(`Checking negative endDelay path existance for ${ targetClassName }`);
+    const negativeEndDelayPathEl =
+      animationItemEl.querySelector(".animation-negative-end-delay-path");
+
+    if (expectedPath) {
+      ok(negativeEndDelayPathEl,
+         "The negative endDelay path element should be in animation item element");
+      const pathEl = negativeEndDelayPathEl.querySelector("path");
+      assertPathSegments(pathEl, true, expectedPath);
+    } else {
+      ok(!negativeEndDelayPathEl,
+         "The negative endDelay path element should not be in animation item element");
+    }
+  }
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/animation/test/browser_animation_SummaryGraph_compositor.js
@@ -0,0 +1,49 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that when animations displayed in the timeline are running on the
+// compositor, they get a special icon and information in the tooltip.
+
+add_task(async function () {
+  await addTab(URL_ROOT + "doc_simple_animation.html");
+
+  const { inspector, panel } = await openAnimationInspector();
+
+  info("Select a test node we know has an animation running on the compositor");
+  await selectNodeAndWaitForAnimations(".compositor-all", inspector);
+
+  const summaryGraphEl = panel.querySelector(".animation-summary-graph");
+  ok(summaryGraphEl.classList.contains("compositor"),
+     "The element has the compositor css class");
+  ok(hasTooltip(summaryGraphEl,
+                ANIMATION_L10N.getStr("player.allPropertiesOnCompositorTooltip")),
+     "The element has the right tooltip content");
+
+  info("Select a node we know doesn't have an animation on the compositor");
+  await selectNodeAndWaitForAnimations(".no-compositor", inspector);
+
+  ok(!summaryGraphEl.classList.contains("compositor"),
+     "The element does not have the compositor css class");
+  ok(!hasTooltip(summaryGraphEl,
+                 ANIMATION_L10N.getStr("player.allPropertiesOnCompositorTooltip")),
+     "The element does not have oncompositor tooltip content");
+  ok(!hasTooltip(summaryGraphEl,
+                 ANIMATION_L10N.getStr("player.somePropertiesOnCompositorTooltip")),
+     "The element does not have oncompositor tooltip content");
+
+  info("Select a node we know has animation on the compositor and not on the compositor");
+  await selectNodeAndWaitForAnimations(".compositor-notall", inspector);
+
+  ok(summaryGraphEl.classList.contains("compositor"),
+     "The element has the compositor css class");
+  ok(hasTooltip(summaryGraphEl,
+                ANIMATION_L10N.getStr("player.somePropertiesOnCompositorTooltip")),
+     "The element has the right tooltip content");
+});
+
+function hasTooltip(summaryGraphEl, expected) {
+  const tooltip = summaryGraphEl.getAttribute("title");
+  return tooltip.includes(expected);
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/animation/test/browser_animation_SummaryGraph_tooltip.js
@@ -0,0 +1,305 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for existance and content of tooltip on summary graph element.
+
+const TEST_CASES = [
+  {
+    targetClassName: "cssanimation-normal",
+    expectedResult: {
+      nameAndType: "cssanimation - CSS Animation",
+      duration: "100s",
+      isAllOnCompositor: true,
+    },
+  },
+  {
+    targetClassName: "cssanimation-linear",
+    expectedResult: {
+      nameAndType: "cssanimation - CSS Animation",
+      duration: "100s",
+      animationTimingFunction: "linear",
+      isAllOnCompositor: true,
+    },
+  },
+  {
+    targetClassName: "delay-positive",
+    expectedResult: {
+      nameAndType: "test-delay-animation - Script Animation",
+      delay: "50s",
+      duration: "100s",
+      isAllOnCompositor: true,
+    },
+  },
+  {
+    targetClassName: "delay-negative",
+    expectedResult: {
+      nameAndType: "test-negative-delay-animation - Script Animation",
+      delay: "-50s",
+      duration: "100s",
+      isAllOnCompositor: true,
+    },
+  },
+  {
+    targetClassName: "easing-step",
+    expectedResult: {
+      nameAndType: "Script Animation",
+      duration: "100s",
+      easing: "steps(2)",
+      isAllOnCompositor: true,
+    },
+  },
+  {
+    targetClassName: "enddelay-positive",
+    expectedResult: {
+      nameAndType: "Script Animation",
+      duration: "100s",
+      endDelay: "50s",
+      isAllOnCompositor: true,
+    },
+  },
+  {
+    targetClassName: "enddelay-negative",
+    expectedResult: {
+      nameAndType: "Script Animation",
+      duration: "100s",
+      endDelay: "-50s",
+      isAllOnCompositor: true,
+    },
+  },
+  {
+    targetClassName: "enddelay-with-fill-forwards",
+    expectedResult: {
+      nameAndType: "Script Animation",
+      duration: "100s",
+      endDelay: "50s",
+      fill: "forwards",
+      isAllOnCompositor: true,
+    },
+  },
+  {
+    targetClassName: "enddelay-with-iterations-infinity",
+    expectedResult: {
+      nameAndType: "Script Animation",
+      duration: "100s",
+      endDelay: "50s",
+      iterations: "\u221E",
+      isAllOnCompositor: true,
+    },
+  },
+  {
+    targetClassName: "direction-alternate-with-iterations-infinity",
+    expectedResult: {
+      nameAndType: "Script Animation",
+      duration: "100s",
+      direction: "alternate",
+      iterations: "\u221E",
+      isAllOnCompositor: true,
+    },
+  },
+  {
+    targetClassName: "direction-alternate-reverse-with-iterations-infinity",
+    expectedResult: {
+      nameAndType: "Script Animation",
+      duration: "100s",
+      direction: "alternate-reverse",
+      iterations: "\u221E",
+      isAllOnCompositor: true,
+    },
+  },
+  {
+    targetClassName: "direction-reverse-with-iterations-infinity",
+    expectedResult: {
+      nameAndType: "Script Animation",
+      duration: "100s",
+      direction: "reverse",
+      iterations: "\u221E",
+      isAllOnCompositor: true,
+    },
+  },
+  {
+    targetClassName: "fill-backwards",
+    expectedResult: {
+      nameAndType: "Script Animation",
+      duration: "100s",
+      fill: "backwards",
+      isAllOnCompositor: true,
+    },
+  },
+  {
+    targetClassName: "fill-backwards-with-delay-iterationstart",
+    expectedResult: {
+      nameAndType: "Script Animation",
+      delay: "50s",
+      duration: "100s",
+      fill: "backwards",
+      iterationStart: "0.5",
+      isAllOnCompositor: true,
+    },
+  },
+  {
+    targetClassName: "fill-both",
+    expectedResult: {
+      nameAndType: "Script Animation",
+      duration: "100s",
+      fill: "both",
+      isAllOnCompositor: true,
+    },
+  },
+  {
+    targetClassName: "fill-both-width-delay-iterationstart",
+    expectedResult: {
+      nameAndType: "Script Animation",
+      delay: "50s",
+      duration: "100s",
+      fill: "both",
+      iterationStart: "0.5",
+      isAllOnCompositor: true,
+    },
+  },
+  {
+    targetClassName: "fill-forwards",
+    expectedResult: {
+      nameAndType: "Script Animation",
+      duration: "100s",
+      fill: "forwards",
+      isAllOnCompositor: true,
+    },
+  },
+  {
+    targetClassName: "iterationstart",
+    expectedResult: {
+      nameAndType: "Script Animation",
+      duration: "100s",
+      iterationStart: "0.5",
+      isAllOnCompositor: true,
+    },
+  },
+  {
+    targetClassName: "no-compositor",
+    expectedResult: {
+      nameAndType: "Script Animation",
+      duration: "100s",
+    },
+  },
+  {
+    targetClassName: "keyframes-easing-step",
+    expectedResult: {
+      nameAndType: "Script Animation",
+      duration: "100s",
+      isAllOnCompositor: true,
+    },
+  },
+];
+
+add_task(async function () {
+  await addTab(URL_ROOT + "doc_multi_timings.html");
+
+  const { panel } = await openAnimationInspector();
+
+  for (const testCase of TEST_CASES) {
+    const {
+      expectedResult,
+      targetClassName,
+    } = testCase;
+
+    const animationItemEl =
+      findAnimationItemElementsByTargetClassName(panel, targetClassName);
+    const summaryGraphEl = animationItemEl.querySelector(".animation-summary-graph");
+
+    info(`Checking tooltip for ${ targetClassName }`);
+    ok(summaryGraphEl.hasAttribute("title"),
+       "Summary graph should have 'title' attribute");
+
+    const tooltip = summaryGraphEl.getAttribute("title");
+    const {
+      animationTimingFunction,
+      delay,
+      easing,
+      endDelay,
+      direction,
+      duration,
+      fill,
+      iterations,
+      iterationStart,
+      nameAndType,
+      isAllOnCompositor,
+    } = expectedResult;
+
+    ok(tooltip.startsWith(nameAndType), "Tooltip should start with name and type");
+
+    if (animationTimingFunction) {
+      const expected = `Animation timing function: ${ animationTimingFunction }`;
+      ok(tooltip.includes(expected), `Tooltip should include '${ expected }'`);
+    } else {
+      ok(!tooltip.includes("Animation timing function:"),
+         "Tooltip should not include animation timing function");
+    }
+
+    if (delay) {
+      const expected = `Delay: ${ delay }`;
+      ok(tooltip.includes(expected), `Tooltip should include '${ expected }'`);
+    } else {
+      ok(!tooltip.includes("Delay:"), "Tooltip should not include delay");
+    }
+
+    if (direction) {
+      const expected = `Direction: ${ direction }`;
+      ok(tooltip.includes(expected), `Tooltip should include '${ expected }'`);
+    } else {
+      ok(!tooltip.includes("Direction:"), "Tooltip should not include delay");
+    }
+
+    if (duration) {
+      const expected = `Duration: ${ duration }`;
+      ok(tooltip.includes(expected), `Tooltip should include '${ expected }'`);
+    } else {
+      ok(!tooltip.includes("Duration:"), "Tooltip should not include delay");
+    }
+
+    if (easing) {
+      const expected = `Overall easing: ${ easing }`;
+      ok(tooltip.includes(expected), `Tooltip should include '${ expected }'`);
+    } else {
+      ok(!tooltip.includes("Overall easing:"), "Tooltip should not include easing");
+    }
+
+    if (endDelay) {
+      const expected = `End delay: ${ endDelay }`;
+      ok(tooltip.includes(expected), `Tooltip should include '${ expected }'`);
+    } else {
+      ok(!tooltip.includes("End delay:"), "Tooltip should not include endDelay");
+    }
+
+    if (fill) {
+      const expected = `Fill: ${ fill }`;
+      ok(tooltip.includes(expected), `Tooltip should include '${ expected }'`);
+    } else {
+      ok(!tooltip.includes("Fill:"), "Tooltip should not include fill");
+    }
+
+    if (iterations) {
+      const expected = `Repeats: ${ iterations }`;
+      ok(tooltip.includes(expected), `Tooltip should include '${ expected }'`);
+    } else {
+      ok(!tooltip.includes("Repeats:"), "Tooltip should not include iterations");
+    }
+
+    if (iterationStart) {
+      const expected = `Iteration start: ${ iterationStart }`;
+      ok(tooltip.includes(expected), `Tooltip should include '${ expected }'`);
+    } else {
+      ok(!tooltip.includes("Iteration start:"),
+         "Tooltip should not include iterationStart");
+    }
+
+    if (isAllOnCompositor) {
+      const expected = "All animation properties are optimized";
+      ok(tooltip.includes(expected), `Tooltip should include '${ expected }'`);
+    } else {
+      ok(!tooltip.includes("optimized"),
+         "Tooltip should not include a message for optmization");
+    }
+  }
+});
--- a/devtools/client/inspector/animation/test/browser_animation_animation_list_exists.js
+++ b/devtools/client/inspector/animation/test/browser_animation_animation_list_exists.js
@@ -26,14 +26,9 @@ add_task(async function () {
   isnot(evenColor, oddColor,
         "Background color of an even animation should be different from odd");
 
   info("Checking list and items existence after select a element which has an animation");
   const animatedNode = await getNodeFront(".animated", inspector);
   await selectNodeAndWaitForAnimations(animatedNode, inspector);
   is(panel.querySelectorAll(".animation-list .animation-item").length, 1,
      "The number of animations displayed should be 1 for .animated element");
-
-  // TODO: We need to add following tests after implement since this test has same role
-  // of animationinspector/test/browser_animation_timeline_ui.js
-  // * name label in animation element existance.
-  // * summary graph in animation element existance.
 });
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/animation/test/doc_multi_timings.html
@@ -0,0 +1,161 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8">
+    <style>
+    div {
+      background-color: lime;
+      height: 100px;
+      width: 100px;
+    }
+
+    .cssanimation-normal {
+      animation: cssanimation 100s;
+    }
+
+    .cssanimation-linear {
+      animation: cssanimation 100s linear;
+    }
+
+    @keyframes cssanimation {
+      from {
+        opacity: 0;
+      }
+      to {
+        opacity: 1;
+      }
+    }
+    </style>
+  </head>
+  <body>
+    <div class="cssanimation-normal"></div>
+    <div class="cssanimation-linear"></div>
+    <script>
+    "use strict";
+
+    const duration = 100000;
+
+    function createAnimation(keyframes, effect, className) {
+      const div = document.createElement("div");
+      div.classList.add(className);
+      document.body.appendChild(div);
+      effect.duration = duration;
+      div.animate(keyframes, effect);
+    }
+
+    createAnimation({ opacity: [0, 1] },
+                    { delay: 50000, id: "test-delay-animation" },
+                    "delay-positive");
+
+    createAnimation({ opacity: [0, 1] },
+                    { delay: -50000, id: "test-negative-delay-animation" },
+                    "delay-negative");
+
+    createAnimation({ opacity: [0, 1] },
+                    { easing: "steps(2)" },
+                    "easing-step");
+
+    createAnimation({ opacity: [0, 1] },
+                    { endDelay: 50000 },
+                    "enddelay-positive");
+
+    createAnimation({ opacity: [0, 1] },
+                    { endDelay: -50000 },
+                    "enddelay-negative");
+
+    createAnimation({ opacity: [0, 1] },
+                    { endDelay: 50000, fill: "forwards" },
+                    "enddelay-with-fill-forwards");
+
+    createAnimation({ opacity: [0, 1] },
+                    { endDelay: 50000, iterations: Infinity },
+                    "enddelay-with-iterations-infinity");
+
+    createAnimation({ opacity: [0, 1] },
+                    { direction: "alternate", iterations: Infinity },
+                    "direction-alternate-with-iterations-infinity");
+
+    createAnimation({ opacity: [0, 1] },
+                    { direction: "alternate-reverse", iterations: Infinity },
+                    "direction-alternate-reverse-with-iterations-infinity");
+
+    createAnimation({ opacity: [0, 1] },
+                    { direction: "reverse", iterations: Infinity },
+                    "direction-reverse-with-iterations-infinity");
+
+    createAnimation({ opacity: [0, 1] },
+                    { fill: "backwards" },
+                    "fill-backwards");
+
+    createAnimation({ opacity: [0, 1] },
+                    { fill: "backwards", delay: 50000, iterationStart: 0.5 },
+                     "fill-backwards-with-delay-iterationstart");
+
+    createAnimation({ opacity: [0, 1] },
+                    { fill: "both" },
+                    "fill-both");
+
+    createAnimation({ opacity: [0, 1] },
+                    { fill: "both", delay: 50000, iterationStart: 0.5 },
+                    "fill-both-width-delay-iterationstart");
+
+    createAnimation({ opacity: [0, 1] },
+                    { fill: "forwards" },
+                    "fill-forwards");
+
+    createAnimation({ opacity: [0, 1] },
+                    { iterationStart: 0.5 },
+                    "iterationstart");
+
+    createAnimation({ width: ["100px", "150px"] },
+                    {},
+                    "no-compositor");
+
+    createAnimation([{ opacity: 0, easing: "steps(2)" }, { opacity: 1 }],
+                    {},
+                    "keyframes-easing-step");
+
+    createAnimation(
+      [
+        {
+          opacity: 0,
+          offset: 0,
+        },
+        {
+          opacity: 1,
+          offset: 0.1,
+          easing: "steps(1)"
+        },
+        {
+          opacity: 0,
+          offset: 0.13,
+        }
+      ],
+      {},
+      "narrow-keyframes");
+
+    createAnimation(
+      [
+        {
+          offset: 0,
+          opacity: 1,
+        },
+        {
+          offset: 0.5,
+          opacity: 1,
+        },
+        {
+          offset: 0.5,
+          easing: "steps(1)",
+          opacity: 0,
+        },
+        {
+          offset: 1,
+          opacity: 1,
+        }
+      ],
+      {},
+      "duplicate-offsets");
+    </script>
+  </body>
+</html>
--- a/devtools/client/inspector/animation/test/head.js
+++ b/devtools/client/inspector/animation/test/head.js
@@ -31,18 +31,18 @@ registerCleanupFunction(() => {
  * Open the toolbox, with the inspector tool visible and the animationinspector
  * sidebar selected.
  *
  * @return {Promise} that resolves when the inspector is ready.
  */
 const openAnimationInspector = async function () {
   const { inspector, toolbox } = await openInspectorSidebarTab(TAB_NAME);
   await inspector.once("inspector-updated");
-  await waitForAllAnimationTargets(inspector);
   const { animationinspector: animationInspector } = inspector;
+  await waitForRendering(animationInspector);
   const panel = inspector.panelWin.document.getElementById("animation-container");
   return { animationInspector, toolbox, inspector, panel };
 };
 
 /**
  * Close the toolbox.
  *
  * @return {Promise} that resolves when the toolbox has closed.
@@ -100,17 +100,17 @@ addTab = async function (url) {
  *                   and animations of its subtree are properly displayed.
  */
 const selectNodeAndWaitForAnimations = async function (data, inspector, reason = "test") {
   // We want to make sure the rest of the test waits for the animations to
   // be properly displayed (wait for all target DOM nodes to be previewed).
   const onUpdated = inspector.once("inspector-updated");
   await selectNode(data, inspector, reason);
   await onUpdated;
-  await waitForAllAnimationTargets(inspector);
+  await waitForRendering(inspector.animationinspector);
 };
 
 /**
  * Set the sidebar width by given parameter.
  *
  * @param {String} width
  *        Change sidebar width by given parameter.
  * @param {InspectorPanel} inspector
@@ -119,20 +119,128 @@ const selectNodeAndWaitForAnimations = a
  */
 const setSidebarWidth = async function (width, inspector) {
   const onUpdated = inspector.toolbox.once("inspector-sidebar-resized");
   inspector.splitBox.setState({ width });
   await onUpdated;
 };
 
 /**
+ * Wait for rendering.
+ *
+ * @param {AnimationInspector} animationInspector
+ */
+const waitForRendering = async function (animationInspector) {
+  await Promise.all([
+    waitForAllAnimationTargets(animationInspector),
+    waitForAllSummaryGraph(animationInspector),
+  ]);
+};
+
+/**
  * Wait for all AnimationTarget components to be fully loaded
  * (fetched their related actor and rendered).
  *
- * @param {Inspector} inspector
+ * @param {AnimationInspector} animationInspector
  */
-const waitForAllAnimationTargets = async function (inspector) {
-  const { animationinspector: animationInspector } = inspector;
-
+const waitForAllAnimationTargets = async function (animationInspector) {
   for (let i = 0; i < animationInspector.animations.length; i++) {
     await animationInspector.once("animation-target-rendered");
   }
 };
+
+/**
+ * Wait for all SummaryGraph components to be fully loaded
+ *
+ * @param {AnimationInspector} inspector
+ */
+const waitForAllSummaryGraph = async function (animationInspector) {
+  for (let i = 0; i < animationInspector.animations.length; i++) {
+    await animationInspector.once("animation-summary-graph-rendered");
+  }
+};
+
+/**
+ * SummaryGraph is constructed by <path> element.
+ * This function checks the vertex of path segments.
+ *
+ * @param {Element} pathEl
+ *        <path> element.
+ * @param {boolean} hasClosePath
+ *        Set true if the path shoud be closing.
+ * @param {Object} expectedValues
+ *        JSON object format. We can test the vertex and color.
+ *        e.g.
+ *        [
+ *          { x: 0, y: 0 },
+ *          { x: 0, y: 1 },
+ *        ]
+ */
+function assertPathSegments(pathEl, hasClosePath, expectedValues) {
+  const pathSegList = pathEl.pathSegList;
+  ok(pathSegList, "The tested element should have pathSegList");
+
+  expectedValues.forEach(expectedValue => {
+    ok(isPassingThrough(pathSegList, expectedValue.x, expectedValue.y),
+       `The path segment of x ${ expectedValue.x }, y ${ expectedValue.y } `
+       + `should be passing through`);
+  });
+
+  if (hasClosePath) {
+    const closePathSeg = pathSegList.getItem(pathSegList.numberOfItems - 1);
+    is(closePathSeg.pathSegType, closePathSeg.PATHSEG_CLOSEPATH,
+       "The last segment should be close path");
+  }
+}
+
+/**
+ * Check whether the given vertex is passing throug on the path.
+ *
+ * @param {pathSegList} pathSegList - pathSegList of <path> element.
+ * @param {float} x - x of vertex.
+ * @param {float} y - y of vertex.
+ * @return {boolean} true: passing through, false: no on the path.
+ */
+function isPassingThrough(pathSegList, x, y) {
+  let previousPathSeg = pathSegList.getItem(0);
+  for (let i = 0; i < pathSegList.numberOfItems; i++) {
+    const pathSeg = pathSegList.getItem(i);
+    if (pathSeg.x === undefined) {
+      continue;
+    }
+    const currentX = parseFloat(pathSeg.x.toFixed(3));
+    const currentY = parseFloat(pathSeg.y.toFixed(3));
+    if (currentX === x && currentY === y) {
+      return true;
+    }
+    const previousX = parseFloat(previousPathSeg.x.toFixed(3));
+    const previousY = parseFloat(previousPathSeg.y.toFixed(3));
+    if (previousX <= x && x <= currentX &&
+        Math.min(previousY, currentY) <= y && y <= Math.max(previousY, currentY)) {
+      return true;
+    }
+    previousPathSeg = pathSeg;
+  }
+  return false;
+}
+
+/**
+ * Return animation item element by target node class.
+ * This function compares betweem animation-target textContent and given className.
+ * Also, this function premises one class name.
+ *
+ * @param {Element} panel - root element of animation inspector.
+ * @param {String} targetClassName - class name of tested element.
+ * @return {Element} animation item element.
+ */
+function findAnimationItemElementsByTargetClassName(panel, targetClassName) {
+  const animationTargetEls = panel.querySelectorAll(".animation-target");
+
+  for (const animationTargetEl of animationTargetEls) {
+    const className = animationTargetEl.textContent.split(".")[1];
+
+    if (className === targetClassName) {
+      return animationTargetEl.closest(".animation-item");
+    }
+  }
+
+  return null;
+}