blob: 1af696b11ac3f37ecdb4e57bdc516e45bbf6e648 [file] [log] [blame]
// Copyright 2017 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 "components/autofill/content/renderer/page_passwords_analyser.h"
#include "chrome/test/base/chrome_render_view_test.h"
#include "components/autofill/content/renderer/page_form_analyser_logger.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/web/web_document.h"
#include "third_party/blink/public/web/web_element_collection.h"
#include "third_party/blink/public/web/web_form_element.h"
namespace autofill {
namespace {
class MockPageFormAnalyserLogger : public PageFormAnalyserLogger {
public:
MockPageFormAnalyserLogger() : PageFormAnalyserLogger(nullptr) {}
virtual ~MockPageFormAnalyserLogger() {}
void Send(std::string message,
ConsoleLevel level,
blink::WebNode node) override {
Send(std::move(message), level,
std::vector<blink::WebNode>{std::move(node)});
}
MOCK_METHOD3(Send,
void(std::string message,
ConsoleLevel level,
std::vector<blink::WebNode> nodes));
MOCK_METHOD0(Flush, void());
};
const char kExpectedDocumentationLink[] = " (More info: https://goo.gl/9p2vKq)";
const char kPasswordFieldNotInForm[] =
"<input type='password' autocomplete='new-password'>";
const char kPasswordFormWithoutUsernameField[] =
"<form>"
" <input type='password' autocomplete='new-password'>"
"</form>";
const char kElementsWithDuplicateIds[] =
"<input id='duplicate'>"
"<input id='duplicate'>";
const char kPasswordFormTooComplex[] =
"<form>"
" <input type='text' autocomplete='username'>"
" <input type='password' autocomplete='current-password'>"
" <input type='text' autocomplete='username'>"
" <input type='password' autocomplete='current-password'>"
" <input type='password' autocomplete='new-password'>"
" <input type='password' autocomplete='new-password'>"
"</form>";
const char kInferredPasswordAutocompleteAttributes[] =
// Login form.
"<form>"
" <input type='text'>"
" <input type='password'>"
"</form>"
// Registration form.
"<form>"
" <input type='text'>"
" <input type='password'>"
" <input type='password'>"
"</form>"
// Change password form.
"<form>"
" <input type='text'>"
" <input type='password'>"
" <input type='password'>"
" <input type='password'>"
"</form>";
const char kInferredUsernameAutocompleteAttributes[] =
// Login form.
"<form>"
" <input type='text'>"
" <input type='password' autocomplete='current-password'>"
"</form>"
// Registration form.
"<form>"
" <input type='text'>"
" <input type='password' autocomplete='new-password'>"
" <input type='password' autocomplete='new-password'>"
"</form>"
// Change password form with username.
"<form>"
" <input type='text'>"
" <input type='password' autocomplete='current-password'>"
" <input type='password' autocomplete='new-password'>"
" <input type='password' autocomplete='new-password'>"
"</form>";
const char kPasswordFieldsWithAndWithoutAutocomplete[] =
"<form>"
" <input type='password'>"
" <input type='text'>"
" <input type='password' autocomplete='current-password'>"
"</form>";
const std::string AutocompleteSuggestionString(const std::string& suggestion) {
return "Input elements should have autocomplete "
"attributes (suggested: \"" +
suggestion + "\"):";
}
} // namespace
class PagePasswordsAnalyserTest : public ChromeRenderViewTest {
protected:
PagePasswordsAnalyserTest()
: mock_logger_(new MockPageFormAnalyserLogger()) {}
void TearDown() override {
elements_.clear();
mock_logger_.reset();
page_passwords_analyser.Reset();
ChromeRenderViewTest::TearDown();
}
void LoadTestCase(const char* html) {
elements_.clear();
LoadHTML(html);
blink::WebLocalFrame* frame = GetMainFrame();
blink::WebElementCollection collection = frame->GetDocument().All();
for (blink::WebElement element = collection.FirstItem(); !element.IsNull();
element = collection.NextItem()) {
elements_.push_back(element);
}
// Remove the <html>, <head> and <body> elements.
elements_.erase(elements_.begin(), elements_.begin() + 3);
}
void Expect(const std::string& message,
const ConsoleLevel level,
const std::vector<size_t>& element_indices) {
std::vector<blink::WebNode> nodes;
std::string documented = message + kExpectedDocumentationLink;
for (size_t index : element_indices)
nodes.push_back(elements_[index]);
EXPECT_CALL(*mock_logger_, Send(documented, level, nodes))
.RetiresOnSaturation();
}
void RunTestCase() {
EXPECT_CALL(*mock_logger_, Flush());
page_passwords_analyser.AnalyseDocumentDOM(GetMainFrame(),
mock_logger_.get());
}
PagePasswordsAnalyser page_passwords_analyser;
private:
DISALLOW_COPY_AND_ASSIGN(PagePasswordsAnalyserTest);
std::vector<blink::WebElement> elements_;
std::unique_ptr<MockPageFormAnalyserLogger> mock_logger_;
};
TEST_F(PagePasswordsAnalyserTest, PasswordFieldNotInForm) {
LoadTestCase(kPasswordFieldNotInForm);
Expect("Password field is not contained in a form:",
PageFormAnalyserLogger::kVerbose, {0});
RunTestCase();
}
TEST_F(PagePasswordsAnalyserTest, PasswordFormWithoutUsernameField) {
LoadTestCase(kPasswordFormWithoutUsernameField);
Expect(
"Password forms should have (optionally hidden) "
"username fields for accessibility:",
PageFormAnalyserLogger::kVerbose, {0});
RunTestCase();
}
TEST_F(PagePasswordsAnalyserTest, ElementsWithDuplicateIds) {
LoadTestCase(kElementsWithDuplicateIds);
Expect("Found 2 elements with non-unique id #duplicate:",
PageFormAnalyserLogger::kWarning, {0, 1});
RunTestCase();
}
TEST_F(PagePasswordsAnalyserTest, PasswordFormTooComplex) {
LoadTestCase(kPasswordFormTooComplex);
Expect(
"Multiple forms should be contained in their own "
"form elements; break up complex forms into ones that represent a "
"single action:",
PageFormAnalyserLogger::kVerbose, {0});
RunTestCase();
}
TEST_F(PagePasswordsAnalyserTest, InferredPasswordAutocompleteAttributes) {
LoadTestCase(kInferredPasswordAutocompleteAttributes);
size_t element_index = 0;
// Login form.
element_index++; // Skip form element.
element_index++; // Skip username field.
Expect(AutocompleteSuggestionString("current-password"),
PageFormAnalyserLogger::kVerbose, {element_index++});
// Registration form.
element_index++; // Skip form element.
element_index++; // Skip username field.
Expect(AutocompleteSuggestionString("new-password"),
PageFormAnalyserLogger::kVerbose, {element_index++});
Expect(AutocompleteSuggestionString("new-password"),
PageFormAnalyserLogger::kVerbose, {element_index++});
// Change password form.
element_index++; // Skip form element.
element_index++; // Skip username field.
Expect(AutocompleteSuggestionString("current-password"),
PageFormAnalyserLogger::kVerbose, {element_index++});
Expect(AutocompleteSuggestionString("new-password"),
PageFormAnalyserLogger::kVerbose, {element_index++});
Expect(AutocompleteSuggestionString("new-password"),
PageFormAnalyserLogger::kVerbose, {element_index++});
RunTestCase();
}
TEST_F(PagePasswordsAnalyserTest, InferredUsernameAutocompleteAttributes) {
LoadTestCase(kInferredUsernameAutocompleteAttributes);
size_t element_index = 0;
// Login form.
element_index++; // Skip form element.
Expect(AutocompleteSuggestionString("username"),
PageFormAnalyserLogger::kVerbose, {element_index++});
element_index++; // Skip already annotated password field.
// Registration form.
element_index++; // Skip form element.
Expect(AutocompleteSuggestionString("username"),
PageFormAnalyserLogger::kVerbose, {element_index++});
element_index++; // Skip already annotated password field.
element_index++; // Skip already annotated password field.
// Change password form with username.
element_index++; // Skip form element.
Expect(AutocompleteSuggestionString("username"),
PageFormAnalyserLogger::kVerbose, {element_index++});
element_index++; // Skip already annotated password field.
element_index++; // Skip already annotated password field.
element_index++; // Skip already annotated password field.
RunTestCase();
}
TEST_F(PagePasswordsAnalyserTest, PasswordFieldWithAndWithoutAutocomplete) {
LoadTestCase(kPasswordFieldsWithAndWithoutAutocomplete);
Expect(
"Multiple forms should be contained in their own "
"form elements; break up complex forms into ones that represent a "
"single action:",
PageFormAnalyserLogger::kVerbose, {0});
RunTestCase();
}
} // namespace autofill