dump_accessibility_tree_browsertest.cc revision 5821806d5e7f356e8fa4b058a389a808ea183019
1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <set>
6#include <string>
7#include <vector>
8
9#include "base/logging.h"
10#include "base/path_service.h"
11#include "base/string_split.h"
12#include "base/string_util.h"
13#include "base/string16.h"
14#include "base/utf_string_conversions.h"
15#include "content/browser/accessibility/browser_accessibility.h"
16#include "content/browser/accessibility/browser_accessibility_manager.h"
17#include "content/browser/accessibility/dump_accessibility_tree_helper.h"
18#include "content/browser/renderer_host/render_view_host_impl.h"
19#include "content/port/browser/render_widget_host_view_port.h"
20#include "content/public/browser/notification_service.h"
21#include "content/public/browser/notification_types.h"
22#include "content/public/browser/web_contents.h"
23#include "content/public/common/content_paths.h"
24#include "content/public/test/test_utils.h"
25#include "content/test/content_browser_test.h"
26#include "content/test/content_browser_test_utils.h"
27#include "content/shell/shell.h"
28#include "testing/gtest/include/gtest/gtest.h"
29
30namespace {
31  static const char kCommentToken = '#';
32  static const char* kMarkSkipFile = "#<skip";
33  static const char* kMarkEndOfFile = "<-- End-of-file -->";
34  static const char* kSignalDiff = "*";
35} // namespace
36
37namespace content {
38
39// This test takes a snapshot of the platform BrowserAccessibility tree and
40// tests it against an expected baseline.
41//
42// The flow of the test is as outlined below.
43// 1. Load an html file from chrome/test/data/accessibility.
44// 2. Read the expectation.
45// 3. Browse to the page and serialize the platform specific tree into a human
46//    readable string.
47// 4. Perform a comparison between actual and expected and fail if they do not
48//    exactly match.
49class DumpAccessibilityTreeTest : public ContentBrowserTest {
50 public:
51  // Utility helper that does a comment aware equality check.
52  // Returns array of lines from expected file which are different.
53  std::vector<int> DiffLines(std::vector<std::string>& expected_lines,
54                             std::vector<std::string>& actual_lines) {
55    int actual_lines_count = actual_lines.size();
56    int expected_lines_count = expected_lines.size();
57    std::vector<int> diff_lines;
58    int i = 0, j = 0;
59    while (i < actual_lines_count && j < expected_lines_count) {
60      if (expected_lines[j].size() == 0 ||
61          expected_lines[j][0] == kCommentToken) {
62        // Skip comment lines and blank lines in expected output.
63        ++j;
64        continue;
65      }
66
67      if (actual_lines[i] != expected_lines[j])
68        diff_lines.push_back(j);
69      ++i;
70      ++j;
71    }
72
73    // Actual file has been fully checked.
74    return diff_lines;
75  }
76
77  void AddDefaultFilters(std::set<string16>* allow_filters,
78                         std::set<string16>* deny_filters) {
79    allow_filters->insert(ASCIIToUTF16("FOCUSABLE"));
80    allow_filters->insert(ASCIIToUTF16("READONLY"));
81  }
82
83  void ParseFilters(const std::string& test_html,
84                    std::set<string16>* allow_filters,
85                    std::set<string16>* deny_filters) {
86    std::vector<std::string> lines;
87    base::SplitString(test_html, '\n', &lines);
88    for (std::vector<std::string>::const_iterator iter = lines.begin();
89         iter != lines.end();
90         ++iter) {
91      const std::string& line = *iter;
92      const std::string& allow_str = helper_.GetAllowString();
93      const std::string& deny_str = helper_.GetDenyString();
94      if (StartsWithASCII(line, allow_str, true))
95        allow_filters->insert(UTF8ToUTF16(line.substr(allow_str.size())));
96      else if (StartsWithASCII(line, deny_str, true))
97        deny_filters->insert(UTF8ToUTF16(line.substr(deny_str.size())));
98    }
99  }
100
101  void RunTest(const FilePath::CharType* file_path);
102
103  DumpAccessibilityTreeHelper helper_;
104};
105
106void DumpAccessibilityTreeTest::RunTest(const FilePath::CharType* file_path) {
107  NavigateToURL(shell(), GURL("about:blank"));
108  RenderWidgetHostViewPort* host_view = static_cast<RenderWidgetHostViewPort*>(
109      shell()->web_contents()->GetRenderWidgetHostView());
110  RenderWidgetHostImpl* host =
111      RenderWidgetHostImpl::From(host_view->GetRenderWidgetHost());
112  RenderViewHostImpl* view_host = static_cast<RenderViewHostImpl*>(host);
113  view_host->set_save_accessibility_tree_for_testing(true);
114  view_host->SetAccessibilityMode(AccessibilityModeComplete);
115
116  // Setup test paths.
117  FilePath dir_test_data;
118  EXPECT_TRUE(PathService::Get(DIR_TEST_DATA, &dir_test_data));
119  FilePath test_path(dir_test_data.Append(FILE_PATH_LITERAL("accessibility")));
120  EXPECT_TRUE(file_util::PathExists(test_path))
121      << test_path.LossyDisplayName();
122
123  FilePath html_file = test_path.Append(FilePath(file_path));
124  // Output the test path to help anyone who encounters a failure and needs
125  // to know where to look.
126  printf("Testing: %s\n", html_file.MaybeAsASCII().c_str());
127
128  std::string html_contents;
129  file_util::ReadFileToString(html_file, &html_contents);
130
131  // Parse filters in the test file.
132  std::set<string16> allow_filters;
133  std::set<string16> deny_filters;
134  AddDefaultFilters(&allow_filters, &deny_filters);
135  ParseFilters(html_contents, &allow_filters, &deny_filters);
136  helper_.SetFilters(allow_filters, deny_filters);
137
138  // Read the expected file.
139  std::string expected_contents_raw;
140  FilePath expected_file =
141    FilePath(html_file.RemoveExtension().value() +
142             helper_.GetExpectedFileSuffix());
143  file_util::ReadFileToString(
144      expected_file,
145      &expected_contents_raw);
146
147  // Tolerate Windows-style line endings (\r\n) in the expected file:
148  // normalize by deleting all \r from the file (if any) to leave only \n.
149  std::string expected_contents;
150  RemoveChars(expected_contents_raw, "\r", &expected_contents);
151
152  if (!expected_contents.compare(0, strlen(kMarkSkipFile), kMarkSkipFile)) {
153    printf("Skipping this test on this platform.\n");
154    return;
155  }
156
157  // Load the page.
158  WindowedNotificationObserver tree_updated_observer(
159      NOTIFICATION_ACCESSIBILITY_LOAD_COMPLETE,
160      NotificationService::AllSources());
161  string16 html_contents16;
162  html_contents16 = UTF8ToUTF16(html_contents);
163  GURL url = GetTestUrl("accessibility",
164                        html_file.BaseName().MaybeAsASCII().c_str());
165  NavigateToURL(shell(), url);
166
167  // Wait for the tree.
168  tree_updated_observer.Wait();
169
170  // Perform a diff (or write the initial baseline).
171  string16 actual_contents_utf16;
172  helper_.DumpAccessibilityTree(
173      host_view->GetBrowserAccessibilityManager()->GetRoot(),
174      &actual_contents_utf16);
175  std::string actual_contents = UTF16ToUTF8(actual_contents_utf16);
176  std::vector<std::string> actual_lines, expected_lines;
177  Tokenize(actual_contents, "\n", &actual_lines);
178  Tokenize(expected_contents, "\n", &expected_lines);
179  // Marking the end of the file with a line of text ensures that
180  // file length differences are found.
181  expected_lines.push_back(kMarkEndOfFile);
182  actual_lines.push_back(kMarkEndOfFile);
183
184  std::vector<int> diff_lines = DiffLines(expected_lines, actual_lines);
185  bool is_different = diff_lines.size() > 0;
186  EXPECT_FALSE(is_different);
187  if (is_different) {
188    // Mark the expected lines which did not match actual output with a *.
189    printf("* Line Expected\n");
190    printf("- ---- --------\n");
191    for (int line = 0, diff_index = 0;
192         line < static_cast<int>(expected_lines.size());
193         ++line) {
194      bool is_diff = false;
195      if (diff_index < static_cast<int>(diff_lines.size()) &&
196          diff_lines[diff_index] == line) {
197        is_diff = true;
198        ++ diff_index;
199      }
200      printf("%1s %4d %s\n", is_diff? kSignalDiff : "", line + 1,
201             expected_lines[line].c_str());
202    }
203    printf("\nActual\n");
204    printf("------\n");
205    printf("%s\n", actual_contents.c_str());
206  }
207
208  if (!file_util::PathExists(expected_file)) {
209    FilePath actual_file =
210      FilePath(html_file.RemoveExtension().value() +
211               helper_.GetActualFileSuffix());
212
213    EXPECT_TRUE(file_util::WriteFile(
214        actual_file, actual_contents.c_str(), actual_contents.size()));
215
216    ADD_FAILURE() << "No expectation found. Create it by doing:\n"
217                  << "mv " << actual_file.LossyDisplayName() << " "
218                  << expected_file.LossyDisplayName();
219  }
220}
221
222IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityA) {
223  RunTest(FILE_PATH_LITERAL("a.html"));
224}
225
226IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityAName) {
227  RunTest(FILE_PATH_LITERAL("a-name.html"));
228}
229
230IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityAOnclick) {
231  RunTest(FILE_PATH_LITERAL("a-onclick.html"));
232}
233
234IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest,
235                       AccessibilityAriaApplication) {
236  RunTest(FILE_PATH_LITERAL("aria-application.html"));
237}
238
239IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityAriaCombobox) {
240  RunTest(FILE_PATH_LITERAL("aria-combobox.html"));
241}
242
243IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityAriaMenu) {
244  RunTest(FILE_PATH_LITERAL("aria-menu.html"));
245}
246
247IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityAWithImg) {
248  RunTest(FILE_PATH_LITERAL("a-with-img.html"));
249}
250
251IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityButtonNameCalc) {
252  RunTest(FILE_PATH_LITERAL("button-name-calc.html"));
253}
254
255// TODO(dmazzoni): rebaseline and enable after this WebKit change is rolled:
256// https://bugs.webkit.org/show_bug.cgi?id=96323
257IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest,
258                       DISABLED_AccessibilityCanvas) {
259  RunTest(FILE_PATH_LITERAL("canvas.html"));
260}
261
262IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest,
263                       AccessibilityCheckboxNameCalc) {
264  RunTest(FILE_PATH_LITERAL("checkbox-name-calc.html"));
265}
266
267// TODO(dimich): Started to fail in Chrome r149732 (crbug 140397)
268#if defined(OS_WIN)
269#define MAYBE_AccessibilityContenteditableDescendants \
270    DISABLED_AccessibilityContenteditableDescendants
271#else
272#define MAYBE_AccessibilityContenteditableDescendants \
273    AccessibilityContenteditableDescendants
274#endif
275
276IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityDiv) {
277  RunTest(FILE_PATH_LITERAL("div.html"));
278}
279
280IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest,
281                       MAYBE_AccessibilityContenteditableDescendants) {
282  RunTest(FILE_PATH_LITERAL("contenteditable-descendants.html"));
283}
284
285IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityFooter) {
286  RunTest(FILE_PATH_LITERAL("footer.html"));
287}
288
289IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityForm) {
290  RunTest(FILE_PATH_LITERAL("form.html"));
291}
292
293IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityHR) {
294  RunTest(FILE_PATH_LITERAL("hr.html"));
295}
296
297IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityInputRange) {
298  RunTest(FILE_PATH_LITERAL("input-range.html"));
299}
300
301IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest,
302                       AccessibilityInputTextNameCalc) {
303  RunTest(FILE_PATH_LITERAL("input-text-name-calc.html"));
304}
305
306IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityLabel) {
307  RunTest(FILE_PATH_LITERAL("label.html"));
308}
309
310IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityListMarkers) {
311  RunTest(FILE_PATH_LITERAL("list-markers.html"));
312}
313
314IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityP) {
315  RunTest(FILE_PATH_LITERAL("p.html"));
316}
317
318IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilitySpinButton) {
319  RunTest(FILE_PATH_LITERAL("spinbutton.html"));
320}
321
322// TODO(dmazzoni): rebaseline and enable after this WebKit change is rolled:
323// https://bugs.webkit.org/show_bug.cgi?id=96323
324IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest,
325                       DISABLED_AccessibilityToggleButton) {
326  RunTest(FILE_PATH_LITERAL("togglebutton.html"));
327}
328
329IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityUl) {
330  RunTest(FILE_PATH_LITERAL("ul.html"));
331}
332
333}  // namespace content
334