blob: 0247eb0e3b1f18e747bf2a485070cd45e1cf653b [file] [log] [blame]
// Copyright (c) 2012 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 <algorithm>
#include <vector>
#include "base/bind.h"
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/macros.h"
#include "base/no_destructor.h"
#include "base/path_service.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/autofill/content/browser/content_autofill_driver.h"
#include "components/autofill/content/browser/content_autofill_driver_factory.h"
#include "components/autofill/core/browser/autofill_experiments.h"
#include "components/autofill/core/browser/autofill_manager.h"
#include "components/autofill/core/browser/data_driven_test.h"
#include "components/autofill/core/browser/form_structure.h"
#include "components/autofill/core/common/autofill_features.h"
#include "content/public/common/content_switches.h"
#include "net/http/http_status_code.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "url/gurl.h"
#if defined(OS_MACOSX)
#include "base/mac/foundation_util.h"
#endif
namespace autofill {
namespace {
using net::test_server::BasicHttpResponse;
using net::test_server::HttpRequest;
using net::test_server::HttpResponse;
const base::FilePath::CharType kTestName[] = FILE_PATH_LITERAL("heuristics");
const std::set<base::FilePath::StringType>& GetFailingTestNames() {
static auto* failing_test_names = new std::set<base::FilePath::StringType>{};
return *failing_test_names;
}
const base::FilePath& GetTestDataDir() {
static base::NoDestructor<base::FilePath> dir([]() {
base::FilePath dir;
base::PathService::Get(base::DIR_SOURCE_ROOT, &dir);
dir = dir.AppendASCII("components").AppendASCII("test").AppendASCII("data");
return dir;
}());
return *dir;
}
const base::FilePath GetInputDir() {
static base::FilePath input_dir = GetTestDataDir()
.AppendASCII("autofill")
.Append(kTestName)
.AppendASCII("input");
return input_dir;
}
std::vector<base::FilePath> GetTestFiles() {
base::FileEnumerator input_files(GetInputDir(), false,
base::FileEnumerator::FILES);
std::vector<base::FilePath> files;
for (base::FilePath input_file = input_files.Next(); !input_file.empty();
input_file = input_files.Next()) {
files.push_back(input_file);
}
std::sort(files.begin(), files.end());
#if defined(OS_MACOSX)
base::mac::ClearAmIBundledCache();
#endif // defined(OS_MACOSX)
return files;
}
std::string FormStructuresToString(
const AutofillManager::FormStructureMap& forms) {
std::map<base::TimeTicks, const FormStructure*> sorted_forms;
for (const auto& form_kv : forms) {
const auto* form = form_kv.second.get();
EXPECT_TRUE(
sorted_forms.emplace(form->form_parsed_timestamp(), form).second);
}
std::string forms_string;
for (const auto& kv : sorted_forms) {
const auto* form = kv.second;
for (const auto& field : *form) {
forms_string += field->Type().ToString();
forms_string += " | " + base::UTF16ToUTF8(field->name);
forms_string += " | " + base::UTF16ToUTF8(field->label);
forms_string += " | " + base::UTF16ToUTF8(field->value);
forms_string += " | " + field->section;
forms_string += "\n";
}
}
return forms_string;
}
} // namespace
// A data-driven test for verifying Autofill heuristics. Each input is an HTML
// file that contains one or more forms. The corresponding output file lists the
// heuristically detected type for each field.
class FormStructureBrowserTest
: public InProcessBrowserTest,
public DataDrivenTest,
public ::testing::WithParamInterface<base::FilePath> {
protected:
FormStructureBrowserTest();
~FormStructureBrowserTest() override;
// InProcessBrowserTest
void SetUpCommandLine(base::CommandLine* command_line) override;
// BrowserTestBase
void SetUpOnMainThread() override;
// DataDrivenTest:
void GenerateResults(const std::string& input, std::string* output) override;
private:
std::unique_ptr<HttpResponse> HandleRequest(const HttpRequest& request);
base::test::ScopedFeatureList feature_list_;
// The response content to be returned by the embedded test server. Note that
// this is populated in the main thread as a part of the setup in the
// GenerateResults method but it is consumed later in the IO thread by the
// embedded test server to generate the response.
std::string html_content_;
DISALLOW_COPY_AND_ASSIGN(FormStructureBrowserTest);
};
FormStructureBrowserTest::FormStructureBrowserTest()
: DataDrivenTest(GetTestDataDir()) {
feature_list_.InitWithFeatures(
// Enabled
{},
// Disabled
{autofill::features::kAutofillEnforceMinRequiredFieldsForHeuristics,
autofill::features::kAutofillEnforceMinRequiredFieldsForQuery,
autofill::features::kAutofillEnforceMinRequiredFieldsForUpload,
autofill::features::kAutofillRestrictUnownedFieldsToFormlessCheckout});
}
FormStructureBrowserTest::~FormStructureBrowserTest() {
}
void FormStructureBrowserTest::SetUpCommandLine(
base::CommandLine* command_line) {
InProcessBrowserTest::SetUpCommandLine(command_line);
// Suppress most output logs because we can't really control the output for
// arbitrary test sites.
command_line->AppendSwitchASCII(switches::kLoggingLevel, "2");
}
void FormStructureBrowserTest::SetUpOnMainThread() {
InProcessBrowserTest::SetUpOnMainThread();
embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
&FormStructureBrowserTest::HandleRequest, base::Unretained(this)));
ASSERT_TRUE(embedded_test_server()->Start());
}
void FormStructureBrowserTest::GenerateResults(const std::string& input,
std::string* output) {
// Cache the content to be returned by the embedded test server. This data
// is readonly after this point.
html_content_.clear();
html_content_.reserve(input.length());
for (const char c : input) {
// Strip `\n`, `\t`, `\r` from |html| to match old `data:` URL behavior.
// TODO(crbug/239819): the tests expect weird concatenation behavior based
// legacy data URL behavior. Fix this so the the tests better represent
// the parsing being done in the wild.
if (c != '\r' && c != '\n' && c != '\t')
html_content_.push_back(c);
}
// Navigate to the test html content.
ASSERT_NO_FATAL_FAILURE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL("/test.html")));
// Dump the form fields (and their inferred field types).
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ContentAutofillDriver* autofill_driver =
ContentAutofillDriverFactory::FromWebContents(web_contents)
->DriverForFrame(web_contents->GetMainFrame());
ASSERT_NE(nullptr, autofill_driver);
AutofillManager* autofill_manager = autofill_driver->autofill_manager();
ASSERT_NE(nullptr, autofill_manager);
*output = FormStructuresToString(autofill_manager->form_structures());
}
std::unique_ptr<HttpResponse> FormStructureBrowserTest::HandleRequest(
const HttpRequest& request) {
auto response = std::make_unique<BasicHttpResponse>();
response->set_code(net::HTTP_OK);
response->set_content(html_content_);
response->set_content_type("text/html; charset=utf-8");
return std::move(response);
}
IN_PROC_BROWSER_TEST_P(FormStructureBrowserTest, DataDrivenHeuristics) {
// Prints the path of the test to be executed.
LOG(INFO) << GetParam().MaybeAsASCII();
bool is_expected_to_pass =
!base::ContainsKey(GetFailingTestNames(), GetParam().BaseName().value());
RunOneDataDrivenTest(GetParam(), GetOutputDirectory(kTestName),
is_expected_to_pass);
}
INSTANTIATE_TEST_SUITE_P(AllForms,
FormStructureBrowserTest,
testing::ValuesIn(GetTestFiles()));
} // namespace autofill