// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "content/browser/accessibility/dump_accessibility_browsertest_base.h" #include #include #include #include "base/path_service.h" #include "base/strings/string16.h" #include "base/strings/string_split.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "content/browser/accessibility/accessibility_tree_formatter.h" #include "content/browser/accessibility/browser_accessibility.h" #include "content/browser/accessibility/browser_accessibility_manager.h" #include "content/browser/accessibility/browser_accessibility_state_impl.h" #include "content/browser/web_contents/web_contents_impl.h" #include "content/public/browser/web_contents.h" #include "content/public/common/content_paths.h" #include "content/public/common/url_constants.h" #include "content/public/test/content_browser_test.h" #include "content/public/test/content_browser_test_utils.h" #include "content/shell/browser/shell.h" #include "content/test/accessibility_browser_test_utils.h" namespace content { namespace { const char kCommentToken = '#'; const char kMarkSkipFile[] = "#( shell()->web_contents()); AccessibilityTreeFormatter formatter( web_contents->GetRootBrowserAccessibilityManager()->GetRoot()); std::vector filters; filters.push_back(Filter(base::ASCIIToUTF16("*"), Filter::ALLOW)); formatter.SetFilters(filters); formatter.set_show_ids(true); base::string16 ax_tree_dump; formatter.FormatAccessibilityTree(&ax_tree_dump); return ax_tree_dump; } std::vector DumpAccessibilityTestBase::DiffLines( const std::vector& expected_lines, const std::vector& actual_lines) { int actual_lines_count = actual_lines.size(); int expected_lines_count = expected_lines.size(); std::vector diff_lines; int i = 0, j = 0; while (i < actual_lines_count && j < expected_lines_count) { if (expected_lines[j].size() == 0 || expected_lines[j][0] == kCommentToken) { // Skip comment lines and blank lines in expected output. ++j; continue; } if (actual_lines[i] != expected_lines[j]) diff_lines.push_back(j); ++i; ++j; } // Actual file has been fully checked. return diff_lines; } void DumpAccessibilityTestBase::ParseHtmlForExtraDirectives( const std::string& test_html, std::vector* filters, std::string* wait_for) { for (const std::string& line : base::SplitString(test_html, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL)) { const std::string& allow_empty_str = AccessibilityTreeFormatter::GetAllowEmptyString(); const std::string& allow_str = AccessibilityTreeFormatter::GetAllowString(); const std::string& deny_str = AccessibilityTreeFormatter::GetDenyString(); const std::string& wait_str = "@WAIT-FOR:"; if (base::StartsWith(line, allow_empty_str, base::CompareCase::SENSITIVE)) { filters->push_back( Filter(base::UTF8ToUTF16(line.substr(allow_empty_str.size())), Filter::ALLOW_EMPTY)); } else if (base::StartsWith(line, allow_str, base::CompareCase::SENSITIVE)) { filters->push_back(Filter(base::UTF8ToUTF16( line.substr(allow_str.size())), Filter::ALLOW)); } else if (base::StartsWith(line, deny_str, base::CompareCase::SENSITIVE)) { filters->push_back(Filter(base::UTF8ToUTF16( line.substr(deny_str.size())), Filter::DENY)); } else if (base::StartsWith(line, wait_str, base::CompareCase::SENSITIVE)) { *wait_for = line.substr(wait_str.size()); } } } void DumpAccessibilityTestBase::RunTest( const base::FilePath file_path, const char* file_dir) { // Disable the "hot tracked" state (set when the mouse is hovering over // an object) because it makes test output change based on the mouse position. BrowserAccessibilityStateImpl::GetInstance()-> set_disable_hot_tracking_for_testing(true); NavigateToURL(shell(), GURL(url::kAboutBlankURL)); // Output the test path to help anyone who encounters a failure and needs // to know where to look. printf("Testing: %s\n", file_path.MaybeAsASCII().c_str()); std::string html_contents; base::ReadFileToString(file_path, &html_contents); // Read the expected file. std::string expected_contents_raw; base::FilePath expected_file = base::FilePath(file_path.RemoveExtension().value() + AccessibilityTreeFormatter::GetExpectedFileSuffix()); base::ReadFileToString(expected_file, &expected_contents_raw); // Tolerate Windows-style line endings (\r\n) in the expected file: // normalize by deleting all \r from the file (if any) to leave only \n. std::string expected_contents; base::RemoveChars(expected_contents_raw, "\r", &expected_contents); if (!expected_contents.compare(0, strlen(kMarkSkipFile), kMarkSkipFile)) { printf("Skipping this test on this platform.\n"); return; } // Parse filters and other directives in the test file. std::string wait_for; AddDefaultFilters(&filters_); ParseHtmlForExtraDirectives(html_contents, &filters_, &wait_for); // Load the page. base::string16 html_contents16; html_contents16 = base::UTF8ToUTF16(html_contents); GURL url = GetTestUrl(file_dir, file_path.BaseName().MaybeAsASCII().c_str()); // If there's a @WAIT-FOR directive, set up an accessibility notification // waiter that returns on any event; we'll stop when we get the text we're // waiting for, or time out. Otherwise just wait specifically for // the "load complete" event. scoped_ptr waiter; if (!wait_for.empty()) { waiter.reset(new AccessibilityNotificationWaiter( shell(), AccessibilityModeComplete, ui::AX_EVENT_NONE)); } else { waiter.reset(new AccessibilityNotificationWaiter( shell(), AccessibilityModeComplete, ui::AX_EVENT_LOAD_COMPLETE)); } // Load the test html. NavigateToURL(shell(), url); // Wait for notifications. If there's a @WAIT-FOR directive, break when // the text we're waiting for appears in the dump, otherwise break after // the first notification, which will be a load complete. do { waiter->WaitForNotification(); if (!wait_for.empty()) { base::string16 tree_dump = DumpUnfilteredAccessibilityTreeAsString(); if (base::UTF16ToUTF8(tree_dump).find(wait_for) != std::string::npos) wait_for.clear(); } } while (!wait_for.empty()); // Call the subclass to dump the output. std::vector actual_lines = Dump(); // Perform a diff (or write the initial baseline). std::vector expected_lines = base::SplitString( expected_contents, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY); // Marking the end of the file with a line of text ensures that // file length differences are found. expected_lines.push_back(kMarkEndOfFile); actual_lines.push_back(kMarkEndOfFile); std::string actual_contents = base::JoinString(actual_lines, "\n"); std::vector diff_lines = DiffLines(expected_lines, actual_lines); bool is_different = diff_lines.size() > 0; EXPECT_FALSE(is_different); if (is_different) { OnDiffFailed(); // Mark the expected lines which did not match actual output with a *. printf("* Line Expected\n"); printf("- ---- --------\n"); for (int line = 0, diff_index = 0; line < static_cast(expected_lines.size()); ++line) { bool is_diff = false; if (diff_index < static_cast(diff_lines.size()) && diff_lines[diff_index] == line) { is_diff = true; ++diff_index; } printf("%1s %4d %s\n", is_diff? kSignalDiff : "", line + 1, expected_lines[line].c_str()); } printf("\nActual\n"); printf("------\n"); printf("%s\n", actual_contents.c_str()); } if (!base::PathExists(expected_file)) { base::FilePath actual_file = base::FilePath(file_path.RemoveExtension().value() + AccessibilityTreeFormatter::GetActualFileSuffix()); EXPECT_TRUE(base::WriteFile( actual_file, actual_contents.c_str(), actual_contents.size())); ADD_FAILURE() << "No expectation found. Create it by doing:\n" << "mv " << actual_file.LossyDisplayName() << " " << expected_file.LossyDisplayName(); } } } // namespace content