blob: eb07c0b592e818065bc7a63419afa04f44573bf7 [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// 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/command_line.h"
#include "base/containers/contains.h"
#include "base/feature_list.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/no_destructor.h"
#include "base/path_service.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.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/content/browser/test_autofill_manager_injector.h"
#include "components/autofill/core/browser/autofill_experiments.h"
#include "components/autofill/core/browser/autofill_test_utils.h"
#include "components/autofill/core/browser/browser_autofill_manager.h"
#include "components/autofill/core/browser/form_structure.h"
#include "components/autofill/core/browser/test_autofill_manager_waiter.h"
#include "components/autofill/core/common/autofill_features.h"
#include "components/autofill/core/common/autofill_payments_features.h"
#include "components/autofill/core/common/unique_ids.h"
#include "components/variations/variations_switches.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_test.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 "testing/data_driven_testing/data_driven_test.h"
#include "third_party/blink/public/common/features.h"
#include "url/gurl.h"
#if BUILDFLAG(IS_MAC)
#include "base/apple/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 kFeatureName[] = FILE_PATH_LITERAL("autofill");
const base::FilePath::CharType kTestName[] = FILE_PATH_LITERAL("heuristics");
// To disable a data driven test, please add the name of the test file
// (i.e., FILE_PATH_LITERAL("NNN_some_site.html")) to the initializer_list given
// to the failing_test_names constructor.
const auto& GetFailingTestNames() {
static std::set<base::FilePath::StringType> failing_test_names{};
return failing_test_names;
}
const base::FilePath& GetTestDataDir() {
static base::NoDestructor<base::FilePath> dir([]() {
base::FilePath dir;
base::PathService::Get(base::DIR_SRC_TEST_DATA_ROOT, &dir);
dir = dir.AppendASCII("components").AppendASCII("test").AppendASCII("data");
return dir;
}());
return *dir;
}
const base::FilePath GetInputDir() {
static base::FilePath input_dir = GetTestDataDir()
.Append(kFeatureName)
.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 BUILDFLAG(IS_MAC)
base::apple::ClearAmIBundledCache();
#endif // BUILDFLAG(IS_MAC)
return files;
}
std::string FormStructuresToString(
const std::map<FormGlobalId, std::unique_ptr<FormStructure>>& forms) {
std::vector<std::string> string_forms;
string_forms.reserve(forms.size());
// The forms are sorted by their global ID, which should make the order
// deterministic.
for (const auto& [form_id, form_structure] : forms) {
std::string string_form;
std::map<std::string, int> section_to_index;
for (const auto& field : *form_structure) {
std::string section = field->section().ToString();
if (field->section().is_from_fieldidentifier()) {
// Normalize the section by replacing the unique but platform-dependent
// integers in `field->section` with consecutive unique integers.
// The section string is of the form "fieldname_id1_id2", where id1, id2
// are platform-dependent and thus need to be substituted.
size_t last_underscore = section.find_last_of('_');
size_t second_last_underscore =
section.find_last_of('_', last_underscore - 1);
int new_section_index = static_cast<int>(section_to_index.size() + 1);
int section_index =
section_to_index.insert(std::make_pair(section, new_section_index))
.first->second;
if (second_last_underscore != std::string::npos) {
section = base::StringPrintf(
"%s%d", section.substr(0, second_last_underscore + 1).c_str(),
section_index);
}
}
string_form += base::JoinString(
{field->Type().ToStringView(), base::UTF16ToUTF8(field->name()),
base::UTF16ToUTF8(field->label()), base::UTF16ToUTF8(field->value()),
section},
" | ");
string_form.push_back('\n');
}
string_forms.push_back(string_form);
}
sort(string_forms.begin(), string_forms.end());
return base::JoinString(string_forms, "\n");
}
} // 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 testing::DataDrivenTest,
public testing::WithParamInterface<base::FilePath> {
public:
FormStructureBrowserTest(const FormStructureBrowserTest&) = delete;
FormStructureBrowserTest& operator=(const FormStructureBrowserTest&) = delete;
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;
content::WebContents* web_contents() {
return browser()->tab_strip_model()->GetActiveWebContents();
}
private:
class TestAutofillManager : public BrowserAutofillManager {
public:
explicit TestAutofillManager(ContentAutofillDriver* driver)
: BrowserAutofillManager(driver, "en-US") {}
TestAutofillManagerWaiter& waiter() { return waiter_; }
private:
TestAutofillManagerWaiter waiter_{*this,
{AutofillManagerEvent::kFormsSeen}};
};
std::unique_ptr<HttpResponse> HandleRequest(const HttpRequest& request);
// 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_;
test::AutofillBrowserTestEnvironment autofill_test_environment_;
TestAutofillManagerInjector<TestAutofillManager> autofill_manager_injector_;
base::test::ScopedFeatureList feature_list_;
};
FormStructureBrowserTest::FormStructureBrowserTest()
: ::testing::DataDrivenTest(GetTestDataDir(), kFeatureName, kTestName) {
feature_list_.InitWithFeatures(
// Enabled
{
// TODO(crbug.com/40128551) Remove once launched.
features::kAutofillUseNewSectioningMethod,
// TODO(crbug.com/40160818) Remove once launched.
features::kAutofillEnableDependentLocalityParsing,
// TODO(crbug.com/40158074) Remove once launched.
features::kAutofillParsingPatternProvider,
features::kAutofillPageLanguageDetection,
// TODO(crbug.com/40741721): Remove once shared labels are launched.
features::kAutofillEnableSupportForParsingWithSharedLabels,
// TODO(crbug.com/40230674): Remove once launched.
features::kAutofillParseVcnCardOnFileStandaloneCvcFields,
// TODO(crbug.com/40220393): Remove once launched.
features::kAutofillEnableSupportForPhoneNumberTrunkTypes,
features::kAutofillInferCountryCallingCode,
// TODO(crbug.com/40266396): Remove once launched.
features::kAutofillEnableExpirationDateImprovements,
// TODO(crbug.com/40279279): Clean up when launched.
features::kAutofillDefaultToCityAndNumber,
// TODO(b/40204601): Clean up when launched.
blink::features::kAutofillIncludeFormElementsInShadowDom,
blink::features::
kAutofillIncludeShadowDomInUnassociatedListedElements,
},
// Disabled
{// TODO(crbug.com/40220393): Remove once launched.
// This feature is part of the AutofillRefinedPhoneNumberTypes rollout.
// As it is not supported on iOS yet, it is disabled.
features::kAutofillConsiderPhoneNumberSeparatorsValidLabels,
// TODO(crbug.com/40222716): Remove once launched. This feature is
// disabled since it is not supported on iOS.
features::kAutofillAlwaysParsePlaceholders});
}
FormStructureBrowserTest::~FormStructureBrowserTest() = default;
void FormStructureBrowserTest::SetUpCommandLine(
base::CommandLine* command_line) {
// Suppress most output logs because we can't really control the output for
// arbitrary test sites.
command_line->AppendSwitchASCII(switches::kLoggingLevel, "2");
command_line->AppendSwitchASCII(
variations::switches::kVariationsOverrideCountry, "us");
}
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.com/40317270): 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);
}
ASSERT_NO_FATAL_FAILURE(ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL("/test.html"))));
// Dump the form fields (and their inferred field types).
TestAutofillManager* autofill_manager =
autofill_manager_injector_[web_contents()];
ASSERT_TRUE(autofill_manager->waiter().Wait(1));
*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);
}
// TODO(https://crbug.com/41493195): Re-enable this test
#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_WIN)
#define MAYBE_DataDrivenHeuristics DISABLED_DataDrivenHeuristics
#else
#define MAYBE_DataDrivenHeuristics DataDrivenHeuristics
#endif
IN_PROC_BROWSER_TEST_P(FormStructureBrowserTest, MAYBE_DataDrivenHeuristics) {
// Prints the path of the test to be executed.
LOG(INFO) << GetParam().MaybeAsASCII();
bool is_expected_to_pass =
!base::Contains(GetFailingTestNames(), GetParam().BaseName().value());
RunOneDataDrivenTest(GetParam(), GetOutputDirectory(), is_expected_to_pass);
}
INSTANTIATE_TEST_SUITE_P(AllForms,
FormStructureBrowserTest,
testing::ValuesIn(GetTestFiles()));
} // namespace autofill