Bug 1406285 - Part 17: Add tests. r=gl
authorDaisuke Akatsuka <dakatsuka@mozilla.com>
Thu, 18 Jan 2018 14:17:55 +0900
changeset 454273 0d98859cc0a6f07ae3dd82af7c252b430d989364
parent 454272 26ebf9ff6aff3c03f462c8dc809eed5de10c2812
child 454274 7971d134b3a351c0d5c32ed2f6c24349a05a5fb6
push id1648
push usermtabara@mozilla.com
push dateThu, 01 Mar 2018 12:45:47 +0000
treeherdermozilla-release@cbb9688c2eeb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgl
bugs1406285
milestone59.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 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;
+}