Bug 1518487 - adding new checks section in the accessibility panel's sidebar. r=pbro
authorYura Zenevich <yura.zenevich@gmail.com>
Tue, 12 Feb 2019 19:40:24 +0000
changeset 458789 090304ca37d9
parent 458788 b8b41d218866
child 458790 7a797eeffeba
push id35548
push useropoprus@mozilla.com
push dateWed, 13 Feb 2019 09:48:26 +0000
treeherdermozilla-central@93e37c529818 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspbro
bugs1518487
milestone67.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 1518487 - adding new checks section in the accessibility panel's sidebar. r=pbro MozReview-Commit-ID: AAKt7zDvom8 Differential Revision: https://phabricator.services.mozilla.com/D19055
devtools/client/accessibility/accessibility-startup.js
devtools/client/accessibility/accessibility.css
devtools/client/accessibility/actions/details.js
devtools/client/accessibility/components/AccessibilityRow.js
devtools/client/accessibility/components/Accessible.js
devtools/client/accessibility/components/Checks.js
devtools/client/accessibility/components/RightSidebar.js
devtools/client/accessibility/components/moz.build
devtools/client/accessibility/index.html
devtools/client/accessibility/reducers/details.js
devtools/client/accessibility/test/browser/browser.ini
devtools/client/accessibility/test/browser/browser_accessibility_mutations.js
devtools/client/accessibility/test/browser/browser_accessibility_relation_navigation.js
devtools/client/accessibility/test/browser/browser_accessibility_reload.js
devtools/client/accessibility/test/browser/browser_accessibility_sidebar.js
devtools/client/accessibility/test/browser/browser_accessibility_sidebar_checks.js
devtools/client/accessibility/test/browser/browser_accessibility_tree.js
devtools/client/accessibility/test/browser/browser_accessibility_tree_nagivation.js
devtools/client/accessibility/test/browser/head.js
devtools/client/locales/en-US/accessibility.properties
--- a/devtools/client/accessibility/accessibility-startup.js
+++ b/devtools/client/accessibility/accessibility-startup.js
@@ -48,19 +48,24 @@ class AccessibilityStartup {
       this._walker = await this._accessibility.getWalker();
 
       this._supports = {};
       // Only works with FF61+ targets
       this._supports.enableDisable =
         await this.target.actorHasMethod("accessibility", "enable");
 
       if (this._supports.enableDisable) {
-        ([ this._supports.relations, this._supports.snapshot ] = await Promise.all([
+        ([
+          this._supports.relations,
+          this._supports.snapshot,
+          this._supports.audit,
+        ] = await Promise.all([
           this.target.actorHasMethod("accessible", "getRelations"),
           this.target.actorHasMethod("accessible", "snapshot"),
+          this.target.actorHasMethod("accessible", "audit"),
         ]));
 
         await this._accessibility.bootstrap();
       }
 
       return true;
     } catch (e) {
       // toolbox may be destroyed during this step.
--- a/devtools/client/accessibility/accessibility.css
+++ b/devtools/client/accessibility/accessibility.css
@@ -5,18 +5,18 @@
 :root {
   --accessibility-font-size: 12px;
   --accessibility-toolbar-height: 24px;
   --accessibility-toolbar-height-tall: 35px;
   --accessibility-toolbar-focus: var(--blue-50);
   --accessibility-toolbar-focus-alpha30: rgba(10, 132, 255, 0.3);
   --accessibility-full-length-minus-splitter: calc(100% - 1px);
   --accessibility-horizontal-padding: 5px;
-  --accessibility-horizontal-indent: 14px;
-  --accessibility-properties-item-width: calc(100% - var(--accessibility-horizontal-padding));
+  --accessibility-horizontal-indent: 20px;
+  --accessibility-properties-item-width: calc(100% - var(--accessibility-horizontal-indent));
   --accessibility-arrow-horizontal-padding: 4px;
   --accessibility-tree-row-height: 21px;
   --accessibility-unfocused-tree-focused-node-background: var(--grey-20);
   --accessibility-unfocused-tree-focused-node-twisty-fill: var(--theme-icon-dimmed-color);
   --accessibility-link-color: var(--blue-60);
   --accessibility-link-color-active: var(--blue-70);
   --accessible-role-active-background-color: var(--blue-50);
   --accessible-role-active-border-color: #FFFFFFB3;
@@ -51,16 +51,20 @@ body {
 }
 
 @keyframes flash-out {
   from {
     background: var(--theme-contrast-background);
   }
 }
 
+.accessible .tree .node .theme-twisty {
+  width: var(--accessibility-horizontal-indent);
+}
+
 .accessible .tree .node.focused .theme-twisty,
 .treeTable .treeRow.selected .theme-twisty {
   fill: var(--theme-selection-color);
 }
 
 .mainFrame .main-panel {
   flex: 1 1 auto;
   overflow: auto;
@@ -340,17 +344,17 @@ body {
 .accessible .tree button {
   display: block;
 }
 
 /* NOTE: total height of the node (height + padding + border + margin) must
    be exactly the same as the value of TREE_ROW_HEIGHT constant in
    devtools/client/accessibility/constants.js */
 .accessible .tree .node {
-  padding: 0 var(--accessibility-horizontal-padding);
+  padding: 0 var(--accessibility-horizontal-indent);
   position: relative;
   display: flex;
   height: var(--accessibility-tree-row-height);
   width: var(--accessibility-properties-item-width);
   cursor: default;
   align-items: center;
 }
 
@@ -447,16 +451,17 @@ body {
   overflow: hidden;
   text-overflow: ellipsis;
 }
 
 .accessible .tree .objectBox-accessible .open-accessibility-inspector,
 .accessible .tree .objectBox-node .open-inspector{
   width: 17px;
   cursor: pointer;
+  flex-shrink: 0;
 }
 
 .accessible .tree .objectBox-object,
 .accessible .tree .objectBox-string,
 .accessible .tree .objectBox-text,
 .accessible .tree .objectBox-table,
 .accessible .tree .objectLink-textNode,
 .accessible .tree .objectLink-event,
@@ -473,16 +478,24 @@ body {
 .accessible .info {
   color: var(--theme-body-color);
   font-size: 110%;
   padding-inline-start: var(--accessibility-arrow-horizontal-padding);
   height: var(--accessibility-toolbar-height-tall);
   line-height: var(--accessibility-toolbar-height-tall);
 }
 
+.checks-empty {
+  font-style: italic;
+  padding: 0.5em 20px;
+  -moz-user-select: none;
+  font-size: 12px;
+  white-space: initial;
+}
+
 /* Color Contrast */
 .accessibility-color-contrast-check,
 .accessibility-color-contrast {
   position: relative;
   display: flex;
   cursor: default;
   height: inherit;
 }
--- a/devtools/client/accessibility/actions/details.js
+++ b/devtools/client/accessibility/actions/details.js
@@ -11,10 +11,11 @@ const { UPDATE_DETAILS } = require("../c
  * @param {Object} dom walker front
  * @param {Object} accessible front
  * @param {Object} list of supported serverside features.
  */
 exports.updateDetails = (domWalker, accessible, supports) =>
   dispatch => Promise.all([
     domWalker.getNodeFromActor(accessible.actorID, ["rawAccessible", "DOMNode"]),
     supports.relations ? accessible.getRelations() : [],
+    supports.audit ? accessible.audit() : {},
   ]).then(response => dispatch({ accessible, type: UPDATE_DETAILS, response }))
     .catch(error => dispatch({ accessible, type: UPDATE_DETAILS, error }));
--- a/devtools/client/accessibility/components/AccessibilityRow.js
+++ b/devtools/client/accessibility/components/AccessibilityRow.js
@@ -65,16 +65,17 @@ class AccessibilityRow extends Component
       dispatch: PropTypes.func.isRequired,
       walker: PropTypes.object,
     };
   }
 
   componentDidMount() {
     const { selected, object } = this.props.member;
     if (selected) {
+      this.unhighlight();
       this.updateAndScrollIntoViewIfNeeded();
       this.highlight(object, { duration: VALUE_HIGHLIGHT_DURATION });
     }
 
     if (this.props.highlighted) {
       this.scrollIntoView();
     }
   }
@@ -82,16 +83,17 @@ class AccessibilityRow extends Component
   /**
    * Update accessible object details that are going to be rendered inside the
    * accessible panel sidebar.
    */
   componentDidUpdate(prevProps) {
     const { selected, object } = this.props.member;
     // If row is selected, update corresponding accessible details.
     if (!prevProps.member.selected && selected) {
+      this.unhighlight();
       this.updateAndScrollIntoViewIfNeeded();
       this.highlight(object, { duration: VALUE_HIGHLIGHT_DURATION });
     }
 
     if (this.props.highlighted) {
       this.scrollIntoView();
     }
 
--- a/devtools/client/accessibility/components/Accessible.js
+++ b/devtools/client/accessibility/components/Accessible.js
@@ -26,17 +26,17 @@ const { REPS, MODE } = require("devtools
 const { Rep, ElementNode, Accessible: AccessibleRep, Obj } = REPS;
 
 const { translateNodeFrontToGrip } = require("devtools/client/inspector/shared/utils");
 
 loader.lazyRequireGetter(this, "openContentLink", "devtools/client/shared/link", true);
 
 const TELEMETRY_NODE_INSPECTED_COUNT = "devtools.accessibility.node_inspected_count";
 
-const TREE_DEPTH_PADDING_INCREMENT = 15;
+const TREE_DEPTH_PADDING_INCREMENT = 20;
 
 class AccessiblePropertyClass extends Component {
   static get propTypes() {
     return {
       accessible: PropTypes.string,
       object: PropTypes.any,
       focused: PropTypes.bool,
       children: PropTypes.func,
new file mode 100644
--- /dev/null
+++ b/devtools/client/accessibility/components/Checks.js
@@ -0,0 +1,86 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+// React
+const { Component, createFactory } = require("devtools/client/shared/vendor/react");
+const { connect } = require("devtools/client/shared/vendor/react-redux");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+const { div } = require("devtools/client/shared/vendor/react-dom-factories");
+
+const List = createFactory(require("devtools/client/shared/components/List").List);
+const ColorContrastCheck =
+  createFactory(require("./ColorContrastAccessibility").ColorContrastCheck);
+const { L10N } = require("../utils/l10n");
+
+function EmptyChecks() {
+  return (
+    div({
+      className: "checks-empty",
+      role: "presentation",
+    }, L10N.getStr("accessibility.checks.empty"))
+  );
+}
+
+// Component that is responsible for rendering accessible audit data in the a11y panel
+// sidebar.
+class Checks extends Component {
+  static get propTypes() {
+    return {
+      audit: PropTypes.object,
+      labelledby: PropTypes.string.isRequired,
+    };
+  }
+
+  contrastRatio(contrastRatio) {
+    return ColorContrastCheck(contrastRatio);
+  }
+
+  render() {
+    const { audit, labelledby } = this.props;
+    if (!audit) {
+      return EmptyChecks();
+    }
+
+    const items = [];
+    for (const name in audit) {
+      // There are going to be various audit reports for this object, sent by the server.
+      // Iterate over them and delegate rendering to the method with the corresponding
+      // name.
+      if (audit[name] && this[name]) {
+        items.push({
+          component: this[name](audit[name]),
+          className: name,
+          key: name,
+        });
+      }
+    }
+
+    if (items.length === 0) {
+      return EmptyChecks();
+    }
+
+    return (
+      div({
+        className: "checks",
+        role: "presentation",
+      }, List({ items, labelledby }))
+    );
+  }
+}
+
+const mapStateToProps = ({ details, ui }) => {
+  if (!ui.supports.audit) {
+    return {};
+  }
+
+  const { audit } = details;
+  if (!audit) {
+    return {};
+  }
+
+  return { audit };
+};
+
+module.exports = connect(mapStateToProps)(Checks);
--- a/devtools/client/accessibility/components/RightSidebar.js
+++ b/devtools/client/accessibility/components/RightSidebar.js
@@ -5,44 +5,53 @@
 
 // React
 const { Component, createFactory } = require("devtools/client/shared/vendor/react");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 const { div } = require("devtools/client/shared/vendor/react-dom-factories");
 
 const { L10N } = require("../utils/l10n");
 const Accessible = createFactory(require("./Accessible"));
+const Accordion = createFactory(require("devtools/client/shared/components/Accordion"));
+const Checks = createFactory(require("./Checks"));
 
 // Component that is responsible for rendering accessible panel's sidebar.
 class RightSidebar extends Component {
   static get propTypes() {
     return {
       walker: PropTypes.object.isRequired,
     };
   }
 
   /**
    * Render the sidebar component.
    * @returns Sidebar React component.
    */
   render() {
-    const headerID = "accessibility-right-sidebar-header";
+    const propertiesHeaderID = "accessibility-properties-header";
+    const checksHeaderID = "accessibility-checks-header";
     const { walker } = this.props;
     return (
       div({
         className: "right-sidebar",
         role: "presentation",
       },
-        div({
-          className: "_header",
-          id: headerID,
-          role: "heading",
-        }, L10N.getStr("accessibility.properties")),
-        div({
-          className: "_content accessible",
-          role: "presentation",
-        }, Accessible({ walker, labelledby: headerID }))
+        Accordion({
+          items: [{
+            className: "checks",
+            component: Checks({ labelledby: checksHeaderID }),
+            header: L10N.getStr("accessibility.checks"),
+            labelledby: checksHeaderID,
+            opened: true,
+          }, {
+            className: "accessible",
+            component: Accessible({ walker, labelledby: propertiesHeaderID }),
+            header: L10N.getStr("accessibility.properties"),
+            labelledby: propertiesHeaderID,
+            opened: true,
+          }],
+        })
       )
     );
   }
 }
 
 module.exports = RightSidebar;
--- a/devtools/client/accessibility/components/moz.build
+++ b/devtools/client/accessibility/components/moz.build
@@ -2,14 +2,16 @@
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DevToolsModules(
     'AccessibilityRow.js',
     'AccessibilityTree.js',
     'Accessible.js',
     'Button.js',
+    'Checks.js',
+    'ColorContrastAccessibility.js',
     'Description.js',
     'LearnMoreLink.js',
     'MainFrame.js',
     'RightSidebar.js',
     'Toolbar.js'
 )
--- a/devtools/client/accessibility/index.html
+++ b/devtools/client/accessibility/index.html
@@ -4,16 +4,18 @@
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 <!DOCTYPE html>
 <html dir="">
 <head>
   <meta charset="utf-8"/>
 
   <link href="resource://devtools/client/accessibility/accessibility.css" rel="stylesheet"/>
   <link href="resource://devtools/client/shared/components/splitter/SplitBox.css" rel="stylesheet" />
+  <link href="resource://devtools/client/shared/components/Accordion.css" rel="stylesheet" />
+  <link href="resource://devtools/client/shared/components/List.css" rel="stylesheet" />
   <link href="resource://devtools/client/shared/components/tree/TreeView.css" rel="stylesheet" />
 
   <script type="application/javascript"
           src="chrome://devtools/content/shared/theme-switching.js"></script>
 </head>
 <body class="theme-body devtools-monospace" role="application">
   <div id="content" role="presentation"></div>
   <script type="text/javascript" src="./main.js"></script>
--- a/devtools/client/accessibility/reducers/details.js
+++ b/devtools/client/accessibility/reducers/details.js
@@ -34,17 +34,17 @@ function details(state = getInitialState
  */
 function onUpdateDetails(state, action) {
   const { accessible, response, error } = action;
   if (error) {
     console.warn("Error fetching DOMNode for accessible", accessible, error);
     return state;
   }
 
-  const [ DOMNode, relationObjects ] = response;
+  const [ DOMNode, relationObjects, audit ] = response;
   const relations = {};
   relationObjects.forEach(({ type, targets }) => {
     relations[type] = targets.length === 1 ? targets[0] : targets;
   });
-  return { accessible, DOMNode, relations };
+  return { accessible, DOMNode, relations, audit };
 }
 
 exports.details = details;
--- a/devtools/client/accessibility/test/browser/browser.ini
+++ b/devtools/client/accessibility/test/browser/browser.ini
@@ -13,10 +13,11 @@ support-files =
 [browser_accessibility_context_menu_browser.js]
 [browser_accessibility_context_menu_inspector.js]
 [browser_accessibility_mutations.js]
 [browser_accessibility_panel_highlighter.js]
 [browser_accessibility_panel_highlighter_multi_tab.js]
 [browser_accessibility_relation_navigation.js]
 [browser_accessibility_reload.js]
 [browser_accessibility_sidebar.js]
+[browser_accessibility_sidebar_checks.js]
 [browser_accessibility_tree.js]
 [browser_accessibility_tree_nagivation.js]
--- a/devtools/client/accessibility/test/browser/browser_accessibility_mutations.js
+++ b/devtools/client/accessibility/test/browser/browser_accessibility_mutations.js
@@ -13,24 +13,24 @@ const TEST_URI = `<html>
     <p id="p">This is a paragraph.</p>
   </body>
 </html>`;
 
 /**
  * Test data has the format of:
  * {
  *   desc     {String}    description for better logging
- *   action   {Function}  An optional action that needs to be performed before
+ *   setup   {Function}  An optional setup that needs to be performed before
  *                        the state of the tree and the sidebar can be checked.
  *   expected {JSON}      An expected states for the tree and the sidebar.
  * }
  */
 const tests = [{
   desc: "Expand first and second rows, select third row.",
-  action: async ({ doc }) => {
+  setup: async ({ doc }) => {
     await toggleRow(doc, 0);
     await toggleRow(doc, 1);
     selectRow(doc, 3);
   },
   expected: {
     tree: [{
       role: "document",
       name: `"Accessibility Panel Test"`,
@@ -53,17 +53,17 @@ const tests = [{
       keyboardShortcut: "",
       childCount: 1,
       indexInParent: 1,
       states: ["selectable text", "opaque", "enabled", "sensitive"],
     },
   },
 }, {
   desc: "Remove a child from a document.",
-  action: async ({ doc, browser }) => {
+  setup: async ({ doc, browser }) => {
     is(doc.querySelectorAll(".treeRow").length, 4, "Tree size is correct.");
     await ContentTask.spawn(browser, {}, () =>
       content.document.getElementById("p").remove());
     await BrowserTestUtils.waitForCondition(() =>
       doc.querySelectorAll(".treeRow").length === 3, "Tree updated.");
   },
   expected: {
     tree: [{
@@ -78,17 +78,17 @@ const tests = [{
     }],
     sidebar: {
       name: "Top level header",
       role: "text leaf",
     },
   },
 }, {
   desc: "Update child's text content.",
-  action: async ({ browser }) => {
+  setup: async ({ browser }) => {
     await ContentTask.spawn(browser, {}, () => {
       content.document.getElementById("h1").textContent = "New Header";
     });
   },
   expected: {
     tree: [{
       role: "document",
       name: `"Accessibility Panel Test"`,
@@ -97,17 +97,17 @@ const tests = [{
       name: `"New Header"`,
     }, {
       role: "text leaf",
       name: `"New Header"`,
     }],
   },
 }, {
   desc: "Select third row in the tree.",
-  action: ({ doc }) => selectRow(doc, 1),
+  setup: ({ doc }) => selectRow(doc, 1),
   expected: {
     sidebar: {
       name: "New Header",
     },
   },
 }];
 
 /**
--- a/devtools/client/accessibility/test/browser/browser_accessibility_relation_navigation.js
+++ b/devtools/client/accessibility/test/browser/browser_accessibility_relation_navigation.js
@@ -13,17 +13,17 @@ const TEST_URI = `<html>
     <p>This is a paragraph.</p>
   </body>
 </html>`;
 
 /**
  * Test data has the format of:
  * {
  *   desc     {String}    description for better logging
- *   action   {Function}  An optional action that needs to be performed before
+ *   setup   {Function}  An optional setup that needs to be performed before
  *                        the state of the tree and the sidebar can be checked.
  *   expected {JSON}      An expected states for the tree and the sidebar.
  * }
  */
 const tests = [{
   desc: "Test the initial accessibility tree and sidebar states.",
   expected: {
     tree: [{
@@ -39,32 +39,32 @@ const tests = [{
       keyboardShortcut: "",
       childCount: 2,
       indexInParent: 0,
       states: ["readonly", "focusable", "opaque", "enabled", "sensitive"],
     },
   },
 }, {
   desc: "Expand first tree node.",
-  action: async ({ doc }) => toggleRow(doc, 0),
+  setup: async ({ doc }) => toggleRow(doc, 0),
   expected: {
     tree: [{
       role: "document",
       name: `"Accessibility Panel Test"`,
     }, {
       role: "heading",
       name: `"Top level header"`,
     }, {
       role: "paragraph",
       name: `""`,
     } ],
   },
 }, {
   desc: "Select second tree node.",
-  action: async ({ doc }) => selectRow(doc, 1),
+  setup: async ({ doc }) => selectRow(doc, 1),
   expected: {
     sidebar: {
       name: "Top level header",
       role: "heading",
       actions: [],
       value: "",
       description: "",
       keyboardShortcut: "",
@@ -76,17 +76,17 @@ const tests = [{
           name: "Accessibility Panel Test",
         },
       },
       states: ["selectable text", "opaque", "enabled", "sensitive"],
     },
   },
 }, {
   desc: "Select containing document.",
-  action: async ({ doc, win }) => {
+  setup: async ({ doc, win }) => {
     const relations = await selectProperty(doc, "/relations");
     EventUtils.sendMouseEvent({ type: "click" }, relations.querySelector(".arrow"), win);
     const containingDocRelation =
       await selectProperty(doc, "/relations/containing document");
     EventUtils.sendMouseEvent({ type: "click" },
       containingDocRelation.querySelector(".open-accessibility-inspector"), win);
   },
   expected: {
--- a/devtools/client/accessibility/test/browser/browser_accessibility_reload.js
+++ b/devtools/client/accessibility/test/browser/browser_accessibility_reload.js
@@ -21,24 +21,24 @@ const TEST_URI_2 = `<html>
   </head>
   <body></body>
 </html>`;
 
 /**
  * Test data has the format of:
  * {
  *   desc     {String}    description for better logging
- *   action   {Function}  An optional action that needs to be performed before
+ *   setup   {Function}  An optional setup that needs to be performed before
  *                        the state of the tree and the sidebar can be checked.
  *   expected {JSON}      An expected states for the tree and the sidebar.
  * }
  */
 const tests = [{
   desc: "Test the initial accessibility tree state after first row is expanded.",
-  action: async ({ doc }) => toggleRow(doc, 0),
+  setup: async ({ doc }) => toggleRow(doc, 0),
   expected: {
     tree: [{
       role: "document",
       name: `"Accessibility Panel Test"`,
     }, {
       role: "heading",
       name: `"Top level header"`,
     }, {
@@ -47,30 +47,30 @@ const tests = [{
     }],
     sidebar: {
       name: "Accessibility Panel Test",
       role: "document",
     },
   },
 }, {
   desc: "Reload the page.",
-  action: async ({ panel }) => reload(panel.target),
+  setup: async ({ panel }) => reload(panel.target),
   expected: {
     tree: [{
       role: "document",
       name: `"Accessibility Panel Test"`,
     }],
     sidebar: {
       name: "Accessibility Panel Test",
       role: "document",
     },
   },
 }, {
   desc: "Navigate to a new page.",
-  action: async ({ panel }) => navigate(panel.target, buildURL(TEST_URI_2)),
+  setup: async ({ panel }) => navigate(panel.target, buildURL(TEST_URI_2)),
   expected: {
     tree: [{
       role: "document",
       name: `"Navigation Accessibility Panel"`,
     }],
     sidebar: {
       name: "Navigation Accessibility Panel",
       role: "document",
--- a/devtools/client/accessibility/test/browser/browser_accessibility_sidebar.js
+++ b/devtools/client/accessibility/test/browser/browser_accessibility_sidebar.js
@@ -10,17 +10,17 @@ const TEST_URI = `<html>
   </head>
   <body></body>
 </html>`;
 
 /**
  * Test data has the format of:
  * {
  *   desc     {String}    description for better logging
- *   action   {Function}  An optional action that needs to be performed before
+ *   setup   {Function}  An optional setup that needs to be performed before
  *                        the state of the tree and the sidebar can be checked.
  *   expected {JSON}      An expected states for the tree and the sidebar.
  * }
  */
 const tests = [{
   desc: "Test the initial accessibility sidebar state.",
   expected: {
     sidebar: {
@@ -32,26 +32,26 @@ const tests = [{
       keyboardShortcut: "",
       childCount: 0,
       indexInParent: 0,
       states: ["readonly", "focusable", "opaque", "enabled", "sensitive"],
     },
   },
 }, {
   desc: "Mark document as disabled for accessibility.",
-  action: async ({ browser }) => ContentTask.spawn(browser, {}, () =>
+  setup: async ({ browser }) => ContentTask.spawn(browser, {}, () =>
     content.document.body.setAttribute("aria-disabled", true)),
   expected: {
     sidebar: {
       states: ["unavailable", "readonly", "focusable", "opaque"],
     },
   },
 }, {
   desc: "Append a new child to the document.",
-  action: async ({ browser }) => ContentTask.spawn(browser, {}, () => {
+  setup: async ({ browser }) => ContentTask.spawn(browser, {}, () => {
     const doc = content.document;
     const button = doc.createElement("button");
     button.textContent = "Press Me!";
     doc.body.appendChild(button);
   }),
   expected: {
     sidebar: {
       childCount: 1,
new file mode 100644
--- /dev/null
+++ b/devtools/client/accessibility/test/browser/browser_accessibility_sidebar_checks.js
@@ -0,0 +1,70 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_URI = `<html>
+  <head>
+    <meta charset="utf-8"/>
+    <title>Accessibility Panel Test</title>
+  </head>
+  <body>
+    <p style="color: red;">Red</p>
+    <p style="color: blue;">Blue</p>
+    <p style="color: gray; background: linear-gradient(#e66465, #9198e5);">Gray</p>
+  </body>
+</html>`;
+
+/**
+ * Test data has the format of:
+ * {
+ *   desc     {String}    description for better logging
+ *   setup    {Function}  An optional setup that needs to be performed before
+ *                        the state of the tree and the sidebar can be checked.
+ *   expected {JSON}      An expected states for the tree and the sidebar.
+ * }
+ */
+const tests = [{
+  desc: "Test the initial accessibility audit state.",
+  expected: {
+    audit: { contrastRatio: null },
+  },
+}, {
+  desc: "Check accessible representing text node in red.",
+  setup: async ({ doc }) => {
+    await toggleRow(doc, 0);
+    await toggleRow(doc, 1);
+    await selectRow(doc, 2);
+  },
+  expected: {
+    audit: {
+      "contrastRatio": {
+        "value": 4.00,
+        "color": [255, 0, 0, 1],
+        "backgroundColor": [255, 255, 255, 1],
+        "isLargeText": false,
+      },
+    },
+  },
+}, {
+  desc: "Check accessible representing text node in blue.",
+  setup: async ({ doc }) => {
+    await toggleRow(doc, 3);
+    await selectRow(doc, 4);
+  },
+  expected: {
+    audit: {
+      "contrastRatio": {
+        "value": 8.59,
+        "color": [0, 0, 255, 1],
+        "backgroundColor": [255, 255, 255, 1],
+        "isLargeText": false,
+      },
+    },
+  },
+}];
+
+/**
+ * Test that checks the Accessibility panel sidebar.
+ */
+addA11yPanelTestsTask(tests, TEST_URI, "Test Accessibility panel sidebar.");
--- a/devtools/client/accessibility/test/browser/browser_accessibility_tree.js
+++ b/devtools/client/accessibility/test/browser/browser_accessibility_tree.js
@@ -13,47 +13,47 @@ const TEST_URI = `<html>
     <p>This is a paragraph.</p>
   </body>
 </html>`;
 
 /**
  * Test data has the format of:
  * {
  *   desc     {String}    description for better logging
- *   action   {Function}  An optional action that needs to be performed before
+ *   setup    {Function}  An optional setup that needs to be performed before
  *                        the state of the tree and the sidebar can be checked.
  *   expected {JSON}      An expected states for the tree and the sidebar.
  * }
  */
 const tests = [{
   desc: "Test the initial accessibility tree state.",
   expected: {
     tree: [{
       role: "document",
       name: `"Accessibility Panel Test"`,
     }],
   },
 }, {
   desc: "Expand first tree node.",
-  action: async ({ doc }) => toggleRow(doc, 0),
+  setup: async ({ doc }) => toggleRow(doc, 0),
   expected: {
     tree: [{
       role: "document",
       name: `"Accessibility Panel Test"`,
     }, {
       role: "heading",
       name: `"Top level header"`,
     }, {
       role: "paragraph",
       name: `""`,
     }],
   },
 }, {
   desc: "Collapse first tree node.",
-  action: async ({ doc }) => toggleRow(doc, 0),
+  setup: async ({ doc }) => toggleRow(doc, 0),
   expected: {
     tree: [{
       role: "document",
       name: `"Accessibility Panel Test"`,
     }],
   },
 }];
 
--- a/devtools/client/accessibility/test/browser/browser_accessibility_tree_nagivation.js
+++ b/devtools/client/accessibility/test/browser/browser_accessibility_tree_nagivation.js
@@ -13,17 +13,17 @@ const TEST_URI = `<html>
     <p>This is a paragraph.</p>
   </body>
 </html>`;
 
 /**
  * Test data has the format of:
  * {
  *   desc     {String}    description for better logging
- *   action   {Function}  An optional action that needs to be performed before
+ *   setup    {Function}  An optional setup that needs to be performed before
  *                        the state of the tree and the sidebar can be checked.
  *   expected {JSON}      An expected states for the tree and the sidebar.
  * }
  */
 const tests = [{
   desc: "Test the initial accessibility tree and sidebar states.",
   expected: {
     tree: [{
@@ -39,32 +39,32 @@ const tests = [{
       keyboardShortcut: "",
       childCount: 2,
       indexInParent: 0,
       states: ["readonly", "focusable", "opaque", "enabled", "sensitive"],
     },
   },
 }, {
   desc: "Expand first tree node.",
-  action: async ({ doc }) => toggleRow(doc, 0),
+  setup: async ({ doc }) => toggleRow(doc, 0),
   expected: {
     tree: [{
       role: "document",
       name: `"Accessibility Panel Test"`,
     }, {
       role: "heading",
       name: `"Top level header"`,
     }, {
       role: "paragraph",
       name: `""`,
     } ],
   },
 }, {
   desc: "Expand second tree node.",
-  action: async ({ doc }) => toggleRow(doc, 1),
+  setup: async ({ doc }) => toggleRow(doc, 1),
   expected: {
     tree: [{
       role: "document",
       name: `"Accessibility Panel Test"`,
     }, {
       role: "heading",
       name: `"Top level header"`,
     }, {
@@ -83,33 +83,33 @@ const tests = [{
       keyboardShortcut: "",
       childCount: 1,
       indexInParent: 0,
       states: ["selectable text", "opaque", "enabled", "sensitive"],
     },
   },
 }, {
   desc: "Select third tree node.",
-  action: ({ doc }) => selectRow(doc, 2),
+  setup: ({ doc }) => selectRow(doc, 2),
   expected: {
     sidebar: {
       name: "Top level header",
       role: "text leaf",
       actions: [],
       value: "",
       description: "",
       keyboardShortcut: "",
       childCount: 0,
       indexInParent: 0,
       states: ["opaque", "enabled", "sensitive"],
     },
   },
 }, {
   desc: "Collapse first tree node.",
-  action: async ({ doc }) => toggleRow(doc, 0),
+  setup: async ({ doc }) => toggleRow(doc, 0),
   expected: {
     tree: [{
       role: "document",
       name: `"Accessibility Panel Test"`,
     }],
     sidebar: {
       name: "Accessibility Panel Test",
       role: "document",
@@ -119,17 +119,17 @@ const tests = [{
       keyboardShortcut: "",
       childCount: 2,
       indexInParent: 0,
       states: ["readonly", "focusable", "opaque", "enabled", "sensitive"],
     },
   },
 }, {
   desc: "Expand first tree node again.",
-  action: async ({ doc }) => toggleRow(doc, 0),
+  setup: async ({ doc }) => toggleRow(doc, 0),
   expected: {
     tree: [{
       role: "document",
       name: `"Accessibility Panel Test"`,
     }, {
       role: "heading",
       name: `"Top level header"`,
     }, {
--- a/devtools/client/accessibility/test/browser/head.js
+++ b/devtools/client/accessibility/test/browser/head.js
@@ -1,16 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /* import-globals-from ../../../shared/test/shared-head.js */
 /* import-globals-from ../../../inspector/test/shared-head.js */
 
 /* global waitUntilState, gBrowser */
-/* exported addTestTab, checkTreeState, checkSidebarState, selectRow,
+/* exported addTestTab, checkTreeState, checkSidebarState, checkAuditState, selectRow,
             toggleRow, addA11yPanelTestsTask, reload, navigate */
 
 "use strict";
 
 // Import framework's shared head.
 Services.scriptloader.loadSubScript(
   "chrome://mochitests/content/browser/devtools/client/shared/test/shared-head.js",
   this);
@@ -200,16 +200,62 @@ function relationsMatch(relations, expec
       }
     }
   }
 
   return true;
 }
 
 /**
+ * When comparing numerical values (for example contrast), we only care about the 2
+ * decimal points.
+ * @param  {String} _
+ *         Key of the property that is parsed.
+ * @param  {Any} value
+ *         Value of the property that is parsed.
+ * @return {Any}
+ *         Newly formatted value in case of the numeric value.
+ */
+function parseNumReplacer(_, value) {
+  if (typeof value === "number") {
+    return value.toFixed(2);
+  }
+
+  return value;
+}
+
+/**
+ * Check the state of the accessibility sidebar audit(checks).
+ * @param  {Object} store         React store for the panel (includes store for
+ *                                the audit).
+ * @param  {Object} expectedState Expected state of the sidebar audit(checks).
+ */
+async function checkAuditState(store, expectedState) {
+  info("Checking audit state.");
+  await waitUntilState(store, ({ details }) => {
+    const { audit } = details;
+
+    for (const key in expectedState) {
+      const expected = expectedState[key];
+      if (expected && typeof expected === "object") {
+        if (JSON.stringify(audit[key], parseNumReplacer) !==
+            JSON.stringify(expected, parseNumReplacer)) {
+          return false;
+        }
+      } else if (audit && audit[key] !== expected) {
+        return false;
+      }
+    }
+
+    ok(true, "Audit state is correct.");
+    return true;
+  });
+}
+
+/**
  * Check the state of the accessibility sidebar.
  * @param  {Object} store         React store for the panel (includes store for
  *                                the sidebar).
  * @param  {Object} expectedState Expected state of the sidebar.
  */
 async function checkSidebarState(store, expectedState) {
   info("Checking sidebar state.");
   await waitUntilState(store, ({ details }) => {
@@ -298,58 +344,63 @@ function selectRow(doc, rowNumber) {
 
 /**
  * Toggle an expandable tree row.
  * @param  {document} doc       panel documnent.
  * @param  {Number}   rowNumber number of the row/tree node to be toggled.
  */
 async function toggleRow(doc, rowNumber) {
   const win = doc.defaultView;
-  const twisty = doc.querySelectorAll(".theme-twisty")[rowNumber];
+  const row = doc.querySelectorAll(".treeRow")[rowNumber];
+  const twisty = row.querySelector(".theme-twisty");
   const expected = !twisty.classList.contains("open");
 
   info(`${expected ? "Expanding" : "Collapsing"} row ${rowNumber}.`);
 
   EventUtils.sendMouseEvent({ type: "click" }, twisty, win);
   await BrowserTestUtils.waitForCondition(() =>
     !twisty.classList.contains("devtools-throbber") &&
     expected === twisty.classList.contains("open"), "Twisty updated.");
 }
 
 /**
- * Iterate over actions/tests structure and test the state of the
+ * Iterate over setups/tests structure and test the state of the
  * accessibility panel.
  * @param  {JSON}   tests test data that has the format of:
  *                    {
  *                      desc     {String}    description for better logging
- *                      action   {Function}  An optional action that needs to be
+ *                      setup    {Function}  An optional setup that needs to be
  *                                           performed before the state of the
  *                                           tree and the sidebar can be checked
  *                      expected {JSON}      An expected states for the tree and
  *                                           the sidebar
  *                    }
  * @param  {Object} env  contains all relevant environment objects (same
  *                       structure as the return value of 'addTestTab' funciton)
  */
 async function runA11yPanelTests(tests, env) {
-  for (const { desc, action, expected } of tests) {
+  for (const { desc, setup, expected } of tests) {
     info(desc);
 
-    if (action) {
-      await action(env);
+    if (setup) {
+      await setup(env);
     }
 
-    const { tree, sidebar } = expected;
+    const { tree, sidebar, audit } = expected;
     if (tree) {
       await checkTreeState(env.doc, tree);
     }
 
     if (sidebar) {
       await checkSidebarState(env.store, sidebar);
     }
+
+    if (typeof audit !== "undefined") {
+      await checkAuditState(env.store, audit);
+    }
   }
 }
 
 /**
  * Build a valid URL from an HTML snippet.
  * @param  {String} uri HTML snippet
  * @return {String}     built URL
  */
@@ -357,17 +408,17 @@ function buildURL(uri) {
   return `data:text/html;charset=UTF-8,${encodeURIComponent(uri)}`;
 }
 
 /**
  * Add a test task based on the test structure and a test URL.
  * @param  {JSON}   tests  test data that has the format of:
  *                    {
  *                      desc     {String}    description for better logging
- *                      action   {Function}  An optional action that needs to be
+ *                      setup   {Function}   An optional setup that needs to be
  *                                           performed before the state of the
  *                                           tree and the sidebar can be checked
  *                      expected {JSON}      An expected states for the tree and
  *                                           the sidebar
  *                    }
  * @param {String}  uri    test URL
  * @param {String}  msg    a message that is printed for the test
  */
--- a/devtools/client/locales/en-US/accessibility.properties
+++ b/devtools/client/locales/en-US/accessibility.properties
@@ -102,16 +102,25 @@ accessibility.description.general.p2=Acc
 # to an older version of accessibility actor.
 accessibility.description.oldVersion=You are connected to a debugger server that is too old. To use Accessibility panel, please connect to the latest debugger server version.
 
 # LOCALIZATION NOTE (accessibility.tree.menu.printToJSON): A title text used when a
 # context menu item for printing an accessible tree to JSON is rendered after triggering a
 # context menu for an accessible tree row.
 accessibility.tree.menu.printToJSON=Print to JSON
 
+# LOCALIZATION NOTE (accessibility.checks): A title text used for header for checks
+# section in Accessibility details sidebar.
+accessibility.checks=Checks
+
+# LOCALIZATION NOTE (accessibility.checks.empty): A title text used for indicating that
+# accessibility checks for a node yielded no results and another node should be
+# selected.
+accessibility.checks.empty=Select another node to continue.
+
 # LOCALIZATION NOTE (accessibility.contrast.header): A title text used for header for
 # checks related to color and contrast.
 accessibility.contrast.header=Color and Contrast
 
 # LOCALIZATION NOTE (accessibility.contrast.error): A title text for the color
 # contrast ratio, used when the tool is unable to calculate the contrast ratio value.
 accessibility.contrast.error=Unable to calculate