blob: ac7f41534288f2674c5280adcfe148e9e9d23ba3 [file] [log] [blame]
// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "build/build_config.h"
#include <string>
#include <tuple>
#include <utility>
#include "base/base_switches.h"
#include "base/command_line.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/field_trial.h"
#include "base/rand_util.h"
#include "base/run_loop.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/mock_entropy_provider.h"
#include "base/test/scoped_feature_list.h"
#include "base/time/time.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/autofill/autofill_uitest.h"
#include "chrome/browser/autofill/autofill_uitest_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_io_data.h"
#include "chrome/browser/scoped_disable_client_side_decorations_for_test.h"
#include "chrome/browser/translate/chrome_translate_client.h"
#include "chrome/browser/translate/translate_service.h"
#include "chrome/browser/translate/translate_test_utils.h"
#include "chrome/browser/ui/autofill/autofill_popup_controller_impl.h"
#include "chrome/browser/ui/autofill/chrome_autofill_client.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/translate/translate_bubble_model.h"
#include "chrome/browser/ui/translate/translate_bubble_test_utils.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/interactive_test_utils.h"
#include "chrome/test/base/test_switches.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_test_utils.h"
#include "components/autofill/core/browser/browser_autofill_manager.h"
#include "components/autofill/core/browser/browser_autofill_manager_test_delegate.h"
#include "components/autofill/core/browser/data_model/autofill_profile.h"
#include "components/autofill/core/browser/test_autofill_clock.h"
#include "components/autofill/core/browser/test_autofill_manager_waiter.h"
#include "components/autofill/core/browser/test_autofill_tick_clock.h"
#include "components/autofill/core/browser/validation.h"
#include "components/autofill/core/common/autofill_clock.h"
#include "components/autofill/core/common/autofill_constants.h"
#include "components/autofill/core/common/autofill_features.h"
#include "components/autofill/core/common/autofill_tick_clock.h"
#include "components/autofill/core/common/autofill_util.h"
#include "components/metrics/content/subprocess_metrics_provider.h"
#include "components/network_session_configurator/common/network_switches.h"
#include "components/translate/core/browser/translate_manager.h"
#include "components/translate/core/common/translate_switches.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_mock_cert_verifier.h"
#include "content/public/test/fenced_frame_test_util.h"
#include "content/public/test/test_renderer_host.h"
#include "content/public/test/test_utils.h"
#include "content/public/test/url_loader_interceptor.h"
#include "net/base/net_errors.h"
#include "net/cert/mock_cert_verifier.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/controllable_http_response.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/switches.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/keycodes/dom/keycode_converter.h"
#include "ui/events/keycodes/dom_us_layout_data.h"
#include "ui/events/keycodes/keyboard_code_conversion.h"
#include "ui/events/keycodes/keyboard_codes.h"
#if BUILDFLAG(IS_CHROMEOS_ASH) && BUILDFLAG(ENABLE_EXTENSIONS)
// Includes for ChromeVox accessibility tests.
#include "chrome/browser/ash/accessibility/accessibility_manager.h"
#include "chrome/browser/ash/accessibility/speech_monitor.h"
#include "chrome/browser/ui/aura/accessibility/automation_manager_aura.h"
#include "extensions/browser/browsertest_util.h"
#include "ui/base/test/ui_controls.h"
#endif // BUILDFLAG(IS_CHROMEOS_ASH) && BUILDFLAG(ENABLE_EXTENSIONS)
using base::ASCIIToUTF16;
using content::URLLoaderInterceptor;
using ::testing::_;
using ::testing::AssertionFailure;
using ::testing::AssertionResult;
using ::testing::AssertionSuccess;
namespace autofill {
namespace {
static const char kTestShippingFormString[] = R"(
<html>
<head>
<!-- Disable extra network request for /favicon.ico -->
<link rel="icon" href="data:,">
</head>
<body>
An example of a shipping address form.
<form action="https://www.example.com/" method="POST" id="shipping">
<label for="firstname">First name:</label>
<input type="text" id="firstname"><br>
<label for="lastname">Last name:</label>
<input type="text" id="lastname"><br>
<label for="address1">Address line 1:</label>
<input type="text" id="address1"><br>
<label for="address2">Address line 2:</label>
<input type="text" id="address2"><br>
<label for="city">City:</label>
<input type="text" id="city"><br>
<label for="state">State:</label>
<select id="state">
<option value="" selected="yes">--</option>
<option value="CA">California</option>
<option value="TX">Texas</option>
</select><br>
<label for="zip">ZIP code:</label>
<input type="text" id="zip"><br>
<label for="country">Country:</label>
<select id="country">
<option value="" selected="yes">--</option>
<option value="CA">Canada</option>
<option value="US">United States</option>
</select><br>
<label for="phone">Phone number:</label>
<input type="text" id="phone"><br>
</form>
)";
// Searches all frames of the primary page in |web_contents| and returns one
// called |name|. If there are none, returns null, if there are more, returns
// an arbitrary one.
content::RenderFrameHost* RenderFrameHostForName(
content::WebContents* web_contents,
const std::string& name) {
return content::FrameMatchingPredicate(
web_contents->GetPrimaryPage(),
base::BindRepeating(&content::FrameMatchesName, name));
}
// Represents a JavaScript expression that evaluates to a HTMLElement.
using ElementExpr = base::StrongAlias<struct ElementExprTag, std::string>;
ElementExpr GetElementById(const std::string& id) {
return ElementExpr(
base::StringPrintf("document.getElementById(`%s`)", id.c_str()));
}
// Represents a field's expected or actual (as extracted from the DOM) id (not
// its name) and value.
struct FieldValue {
std::string id;
std::string value;
};
std::ostream& operator<<(std::ostream& os, const FieldValue& field) {
return os << "{" << field.id << "=" << field.value << "}";
}
// Returns the field IDs and values of a collection of fields.
//
// Note that `control_elements` is *not* the ID of a form, but a JavaScript
// expression that evaluates to a collection of form-control elements, such has
// `document.getElementById('myForm').elements`.
std::vector<FieldValue> GetFieldValues(
const ElementExpr& control_elements,
content::ToRenderFrameHost execution_target) {
std::string script = base::StringPrintf(
R"( const fields = [];
for (const field of %s) {
fields.push({
id: field.id,
value: field.value
});
}
fields;
)",
control_elements->c_str());
content::EvalJsResult r = content::EvalJs(execution_target, script);
DCHECK(r.value.is_list()) << r.error;
std::vector<FieldValue> fields;
for (const base::Value& field : r.value.GetList()) {
fields.push_back({.id = *field.FindStringKey("id"),
.value = *field.FindStringKey("value")});
}
return fields;
}
// Returns the center point of a DOM element.
gfx::Point GetCenter(const ElementExpr& e,
content::ToRenderFrameHost execution_target) {
std::string x_script = base::StringPrintf(
R"( const bounds = (%s).getBoundingClientRect();
Math.floor(bounds.left + bounds.width / 2))",
e->c_str());
std::string y_script = base::StringPrintf(
R"( const bounds = (%s).getBoundingClientRect();
Math.floor(bounds.top + bounds.height / 2)
)",
e->c_str());
int x = content::EvalJs(execution_target, x_script).ExtractInt();
int y = content::EvalJs(execution_target, y_script).ExtractInt();
return gfx::Point(x, y);
}
// Triggers a JavaScript event like 'focus' and waits for the event to happen.
[[nodiscard]] AssertionResult TriggerAndWaitForEvent(
const ElementExpr& e,
const std::string& event_name,
content::ToRenderFrameHost execution_target) {
std::string script = base::StringPrintf(
R"( if (document.readyState === 'complete') {
function handler(e) {
e.target.removeEventListener(e.type, arguments.callee);
domAutomationController.send(true);
}
const target = %s;
target.addEventListener('%s', handler);
target.%s();
} else {
domAutomationController.send(false);
})",
e->c_str(), event_name.c_str(), event_name.c_str());
content::EvalJsResult result = content::EvalJs(
execution_target, script, content::EXECUTE_SCRIPT_USE_MANUAL_REPLY);
if (!result.error.empty()) {
return AssertionFailure() << __func__ << "(): " << result.error;
} else if (false == result) {
return AssertionFailure()
<< __func__ << "(): couldn't trigger " << event_name << " on " << *e;
} else {
return AssertionSuccess();
}
}
// True iff `e` is the deepest active element in the given frame.
//
// "Deepest" refers to the shadow DOM: if an <input> in the shadow DOM is
// focused, then this <input> and the shadow host are active elements, but
// IsFocusedField() only returns true for the <input>.
bool IsFocusedField(const ElementExpr& e,
content::ToRenderFrameHost execution_target) {
std::string script = base::StringPrintf(
"const e = (%s); e === e.getRootNode().activeElement", e->c_str());
return true == content::EvalJs(execution_target, script);
}
// Unfocuses the currently focused field.
[[nodiscard]] AssertionResult BlurFocusedField(
content::ToRenderFrameHost execution_target) {
std::string script = R"(
if (document.activeElement !== null)
document.activeElement.blur();
)";
return content::ExecJs(execution_target, script);
}
// A helper function for focusing a field in AutofillFlow().
// Consider using AutofillFlow() instead.
[[nodiscard]] AssertionResult FocusField(
const ElementExpr& e,
content::ToRenderFrameHost execution_target) {
if (IsFocusedField(e, execution_target)) {
AssertionResult r = BlurFocusedField(execution_target);
if (!r)
return r;
}
return TriggerAndWaitForEvent(e, "focus", execution_target);
}
// Types the characters of `value` after focusing field `e`.
[[nodiscard]] AssertionResult EnterTextIntoField(
const ElementExpr& e,
base::StringPiece value,
AutofillUiTest* test,
content::ToRenderFrameHost execution_target) {
AssertionResult a = FocusField(e, execution_target);
if (!a) {
return a;
}
for (const char c : value) {
ui::DomKey key = ui::DomKey::FromCharacter(c);
if (!test->SendKeyToPageAndWait(key, {})) {
return AssertionFailure()
<< __func__ << "(): Could not type '" << value << "' into " << *e;
}
}
return AssertionSuccess();
}
// Executes `EnterTextIntoField()` for a series of fields.
[[nodiscard]] AssertionResult EnterTextsIntoFields(
std::vector<std::pair<ElementExpr, std::string>> values,
AutofillUiTest* test,
content::ToRenderFrameHost execution_target) {
for (const auto& [element, value] : values) {
AssertionResult a =
EnterTextIntoField(element, value, test, execution_target);
if (!a) {
return AssertionFailure() << __func__ << "(): " << a;
}
}
return AssertionSuccess();
}
// The different ways of triggering the Autofill dropdown.
//
// A difference in their behaviour is that (only) ByArrow() opens implicitly
// selects the top-most suggestion.
struct ShowMethod {
constexpr static ShowMethod ByArrow() { return {.arrow = true}; }
constexpr static ShowMethod ByClick() { return {.click = true}; }
constexpr static ShowMethod ByChar(char c) { return {.character = c}; }
bool selects_first_suggestion() { return arrow; }
// Exactly one of the members should be evaluate to `true`.
const bool arrow = false;
const char character = '\0';
const bool click = false;
};
// We choose the timeout empirically. 250 ms are not enough; tests become flaky:
// in ShowAutofillPopup(), the preview triggered by an "arrow down" sometimes
// only arrives after >250 ms and thus arrives during the DoNothingAndWait(),
// which causes a crash.
constexpr base::TimeDelta kAutofillFlowDefaultTimeout = base::Seconds(2);
struct ShowAutofillPopupParams {
ShowMethod show_method = ShowMethod::ByArrow();
int num_profile_suggestions = 1;
size_t max_tries = 5;
base::TimeDelta timeout = kAutofillFlowDefaultTimeout;
absl::optional<content::ToRenderFrameHost> execution_target = {};
};
// A helper function for showing the popup in AutofillFlow().
// Consider using AutofillFlow() instead.
[[nodiscard]] AssertionResult ShowAutofillPopup(const ElementExpr& e,
AutofillUiTest* test,
ShowAutofillPopupParams p) {
constexpr auto kSuggest = ObservedUiEvents::kSuggestionShown;
constexpr auto kPreview = ObservedUiEvents::kPreviewFormData;
content::ToRenderFrameHost execution_target =
p.execution_target.value_or(test->GetWebContents());
content::RenderFrameHost* rfh = execution_target.render_frame_host();
content::RenderWidgetHostView* view = rfh->GetView();
content::RenderWidgetHost* widget = view->GetRenderWidgetHost();
auto ArrowDown = [&](std::list<ObservedUiEvents> exp) {
constexpr auto kDown = ui::DomKey::ARROW_DOWN;
if (base::Contains(exp, ObservedUiEvents::kSuggestionShown)) {
return test->SendKeyToPageAndWait(kDown, std::move(exp), p.timeout);
} else {
return test->SendKeyToPopupAndWait(kDown, std::move(exp), widget,
p.timeout);
}
};
auto Backspace = [&]() {
return test->SendKeyToPageAndWait(ui::DomKey::BACKSPACE, {}, p.timeout);
};
auto Char = [&](const std::string& code, std::list<ObservedUiEvents> exp) {
ui::DomCode dom_code = ui::KeycodeConverter::CodeStringToDomCode(code);
ui::DomKey dom_key;
ui::KeyboardCode keyboard_code;
CHECK(ui::DomCodeToUsLayoutDomKey(dom_code, ui::EF_SHIFT_DOWN, &dom_key,
&keyboard_code));
return test->SendKeyToPageAndWait(dom_key, dom_code, keyboard_code,
std::move(exp), p.timeout);
};
auto Click = [&](std::list<ObservedUiEvents> exp) {
gfx::Point point = view->TransformPointToRootCoordSpace(GetCenter(e, rfh));
test->test_delegate()->SetExpectations({ObservedUiEvents::kSuggestionShown},
p.timeout);
content::SimulateMouseClickAt(test->GetWebContents(), 0,
blink::WebMouseEvent::Button::kLeft, point);
return test->test_delegate()->Wait();
};
// It seems that due to race conditions with Blink's layouting
// (crbug.com/1175735#c9), the below focus events are sometimes too early:
// Autofill closes the popup right away because it is outside of the content
// area. To work around this, we attempt to bring up the Autofill popup
// multiple times, with some delay.
AssertionResult a = AssertionFailure()
<< __func__ << "(): with " << p.num_profile_suggestions
<< " profile suggestions";
bool field_was_focused_initially = IsFocusedField(e, rfh);
for (size_t i = 1; i <= p.max_tries; ++i) {
a = a << "Iteration " << i << "/" << p.max_tries << ". ";
// A Translate bubble may overlap with the Autofill popup, which causes
// flakiness. See crbug.com/1175735#c10.
// Also, the address-save prompts and others may overlap with the Autofill
// popup. So we preemptively close all bubbles, which however is not
// reliable on Windows.
translate::test_utils::CloseCurrentBubble(test->browser());
TryToCloseAllPrompts(test->GetWebContents());
if (i > 1) {
test->DoNothingAndWaitAndIgnoreEvents(p.timeout);
if (field_was_focused_initially) {
// The Autofill popup may have opened due to a severely delayed event on
// a slow bot. To reset the popup, we re-focus the field.
a << "Trying to re-focus the field. ";
if (AssertionResult b = BlurFocusedField(rfh); !b)
a = a << b;
if (AssertionResult b = FocusField(e, rfh); !b)
a = a << b;
}
}
bool has_preview = 0 < p.num_profile_suggestions;
if (p.show_method.arrow) {
// Press arrow down to open the popup and select first suggestion.
// Depending on the platform, this requires one or two arrow-downs.
if (!IsFocusedField(e, rfh))
return a << "Field " << *e << " must be focused. ";
if (!ShouldAutoselectFirstSuggestionOnArrowDown()) {
if (!ArrowDown({kSuggest})) {
a << "Cannot trigger suggestions by first arrow. ";
continue;
}
if (!(has_preview ? ArrowDown({kPreview}) : ArrowDown({}))) {
a << "Cannot select first suggestion by second arrow. ";
continue;
}
} else if (!(has_preview ? ArrowDown({kSuggest, kPreview})
: ArrowDown({kSuggest}))) {
a << "Cannot trigger and select first suggestion by arrow. ";
continue;
}
} else if (p.show_method.character) {
// Enter character to open the popup, but do not select an option.
// If necessary, delete past iterations character first.
if (!IsFocusedField(e, rfh))
return a << "Field " << *e << " must be focused. ";
if (i > 1 && !Backspace())
a << "Cannot undo past iteration's key. ";
std::string code = std::string("Key") + p.show_method.character;
if (!Char(code, {kSuggest})) {
a << "Cannot trigger suggestions by key. ";
continue;
}
} else if (p.show_method.click) {
// Click item to open the popup, but do not select an option.
if (!Click({kSuggest})) {
a << "Cannot trigger and select first suggestion by click. ";
continue;
}
}
return AssertionSuccess();
}
return a << "Couldn't show Autofill suggestions on " << *e << ". ";
}
struct AutofillSuggestionParams {
int num_profile_suggestions = 1;
int current_index = 0;
int target_index = 0;
base::TimeDelta timeout = kAutofillFlowDefaultTimeout;
absl::optional<content::ToRenderFrameHost> execution_target = {};
};
// A helper function for selecting a suggestion in AutofillFlow().
// Consider using AutofillFlow() instead.
[[nodiscard]] AssertionResult SelectAutofillSuggestion(
const ElementExpr& e,
AutofillUiTest* test,
AutofillSuggestionParams p) {
content::RenderWidgetHost* widget =
p.execution_target.value_or(test->GetWebContents())
.render_frame_host()
->GetView()
->GetRenderWidgetHost();
constexpr auto kPreview = ObservedUiEvents::kPreviewFormData;
auto ArrowDown = [&](std::list<ObservedUiEvents> exp) {
return test->SendKeyToPopupAndWait(ui::DomKey::ARROW_DOWN, std::move(exp),
widget, p.timeout);
};
for (int i = p.current_index + 1; i <= p.target_index; ++i) {
bool has_preview = i < p.num_profile_suggestions;
if (!(has_preview ? ArrowDown({kPreview}) : ArrowDown({}))) {
return AssertionFailure()
<< __func__ << "(): Couldn't go to " << i << "th suggestion with"
<< (has_preview ? "" : "out") << " preview";
}
}
return AssertionSuccess();
}
// A helper function for accepting a suggestion in AutofillFlow().
// Consider using AutofillFlow() instead.
[[nodiscard]] AssertionResult AcceptAutofillSuggestion(
const ElementExpr& e,
AutofillUiTest* test,
AutofillSuggestionParams p) {
content::RenderWidgetHost* widget =
p.execution_target.value_or(test->GetWebContents())
.render_frame_host()
->GetView()
->GetRenderWidgetHost();
// If `kAutofillPopupUseThresholdForKeyboardAndMobileAccept` is enabled,
// then all attempts to accept Autofill suggestions using keyboard "ENTER"
// keystrokes will be ignored for the first 500ms after the popup is first
// shown. This overrides this threshold.
if (base::WeakPtr<AutofillPopupControllerImpl> controller =
ChromeAutofillClient::FromWebContentsForTesting(
test->GetWebContents())
->popup_controller_for_testing()) {
controller->DisableThresholdForTesting(true);
}
constexpr auto kFill = ObservedUiEvents::kFormDataFilled;
auto Enter = [&](std::list<ObservedUiEvents> exp) {
return test->SendKeyToPopupAndWait(ui::DomKey::ENTER, std::move(exp),
widget);
};
bool has_fill = p.target_index < p.num_profile_suggestions;
if (AssertionResult a = SelectAutofillSuggestion(e, test, p); !a)
return a;
if (!(has_fill ? Enter({kFill}) : Enter({}))) {
return AssertionFailure()
<< __func__ << "(): Couldn't accept to " << p.target_index
<< "th suggestion with" << (has_fill ? "" : "out") << " fill";
}
return AssertionSuccess();
}
// An Autofill consists of four stages:
// 1. focusing the field,
// 2. showing the Autofill popup,
// 3. selecting the desired suggestion,
// 4. accepting the selected suggestion.
//
// To reduce flakiness when in Stage 2, this does multiple attempts.
// Depending on the `show_method`, showing may already select the first
// suggestion; see ShowMethod for details.
//
// Selecting a profile suggestion (address or credit card) also triggers
// preview. By contrast, "Clear" and "Manage" do not cause a preview. The
// Autofill flow expects a preview for (only) the indices less than
// `num_profile_suggestions`. The selected `target_index` may be greater or
// equal to `num_profile_suggestions` to select "Clear" or "Manager".
//
// A callback can be set to be executed after each stage. Again note that
// `show_method` may select the first suggestion.
//
// The default `execution_target` is the main frame.
struct AutofillFlowParams {
bool do_focus = true;
bool do_show = true;
bool do_select = true;
bool do_accept = true;
ShowMethod show_method = ShowMethod::ByArrow();
int num_profile_suggestions = 1;
int target_index = 0;
base::RepeatingClosure after_focus = {};
base::RepeatingClosure after_show = {};
base::RepeatingClosure after_select = {};
base::RepeatingClosure after_accept = {};
size_t max_show_tries = 5;
base::TimeDelta timeout = kAutofillFlowDefaultTimeout;
absl::optional<content::ToRenderFrameHost> execution_target = {};
};
[[nodiscard]] AssertionResult AutofillFlow(const ElementExpr& e,
AutofillUiTest* test,
AutofillFlowParams p = {}) {
content::ToRenderFrameHost execution_target =
p.execution_target.value_or(test->GetWebContents());
if (p.do_focus) {
AssertionResult a = FocusField(e, execution_target);
if (!a)
return a;
if (p.after_focus)
p.after_focus.Run();
}
if (p.do_show) {
AssertionResult a =
ShowAutofillPopup(e, test,
{.show_method = p.show_method,
.num_profile_suggestions = p.num_profile_suggestions,
.max_tries = p.max_show_tries,
.timeout = p.timeout,
.execution_target = execution_target});
if (!a)
return a;
if (p.after_show)
p.after_show.Run();
}
if (p.do_select) {
AssertionResult a = SelectAutofillSuggestion(
e, test,
{.num_profile_suggestions = p.num_profile_suggestions,
.current_index = p.show_method.selects_first_suggestion() ? 0 : -1,
.target_index = p.target_index,
.timeout = p.timeout,
.execution_target = execution_target});
if (!a)
return a;
if (p.after_select)
p.after_select.Run();
}
if (p.do_accept) {
AssertionResult a = AcceptAutofillSuggestion(
e, test,
{.num_profile_suggestions = p.num_profile_suggestions,
.current_index = p.target_index,
.target_index = p.target_index,
.timeout = p.timeout,
.execution_target = execution_target});
if (!a)
return a;
if (p.after_accept)
p.after_accept.Run();
}
return AssertionSuccess();
}
const std::vector<FieldValue> kEmptyAddress{
{"firstname", ""}, {"lastname", ""}, {"address1", ""},
{"address2", ""}, {"city", ""}, {"state", ""},
{"zip", ""}, {"country", ""}, {"phone", ""}};
const struct {
const char* first_name = "Milton";
const char* middle_name = "C.";
const char* last_name = "Waddams";
const char* address1 = "4120 Freidrich Lane";
const char* address2 = "Basement";
const char* city = "Austin";
const char* state_short = "TX";
const char* state = "Texas";
const char* zip = "78744";
const char* country = "US";
const char* phone = "15125551234";
} kDefaultAddressValues;
const std::vector<FieldValue> kDefaultAddress{
{"firstname", kDefaultAddressValues.first_name},
{"lastname", kDefaultAddressValues.last_name},
{"address1", kDefaultAddressValues.address1},
{"address2", kDefaultAddressValues.address2},
{"city", kDefaultAddressValues.city},
{"state", kDefaultAddressValues.state_short},
{"zip", kDefaultAddressValues.zip},
{"country", kDefaultAddressValues.country},
{"phone", kDefaultAddressValues.phone}};
// Returns a copy of |fields| except that the value of `update.id` is set to
// `update.value`.
[[nodiscard]] std::vector<FieldValue> MergeValue(std::vector<FieldValue> fields,
const FieldValue& update) {
for (auto& field : fields) {
if (field.id == update.id) {
field.value = update.value;
return fields;
}
}
NOTREACHED();
return fields;
}
// Returns a copy of |fields| merged with |updates|.
[[nodiscard]] std::vector<FieldValue> MergeValues(
std::vector<FieldValue> fields,
const std::vector<FieldValue>& updates) {
for (auto& update : updates)
fields = MergeValue(std::move(fields), update);
return fields;
}
// Matches a container of FieldValues if the `i`th actual FieldValue::value
// matches the `i`th `expected` FieldValue::value.
// As a sanity check, also requires that the `i`th actual FieldValue::id
// starts with the `i`th `expected` FieldValue::id.
[[nodiscard]] auto ValuesAre(const std::vector<FieldValue>& expected) {
auto FieldEq = [](const FieldValue& expected) {
return ::testing::AllOf(
::testing::Field(&FieldValue::id, ::testing::StartsWith(expected.id)),
::testing::Field(&FieldValue::value, ::testing::Eq(expected.value)));
};
std::vector<decltype(FieldEq(expected[0]))> matchers;
for (const FieldValue& field : expected)
matchers.push_back(FieldEq(field));
return ::testing::UnorderedElementsAreArray(matchers);
}
// An object that waits for an observed form-control element to change its value
// to a non-empty string.
//
// See ListenForValueChange() for details.
class ValueWaiter {
public:
static constexpr base::TimeDelta kDefaultTimeout = base::Seconds(5);
ValueWaiter(int waiterId, content::ToRenderFrameHost execution_target)
: waiterId_(waiterId), execution_target_(execution_target) {}
// Returns the non-empty value of the observed form-control element, or
// absl::nullopt if no value change is observed before `timeout`.
[[nodiscard]] absl::optional<std::string> Wait(
base::TimeDelta timeout = kDefaultTimeout) && {
const std::string kFunction = R"(
// Polls the value of `window[observedValueSlots]` and replies with the
// value once its non-`undefined` or `timeoutMillis` have elapsed.
//
// The value is expected to be populated by listenForValueChange().
function pollValue(waiterId, timeoutMillis) {
console.log(`pollValue('${waiterId}', ${timeoutMillis})`);
let interval = undefined;
let timeout = undefined;
function reply(r) {
console.log(`pollValue('${waiterId}', ${timeoutMillis}): `+
`replying '${r}'`);
window.domAutomationController.send(r);
clearTimeout(timeout);
clearInterval(interval);
}
function replyIfSet(r) {
if (r !== undefined)
reply(r);
}
timeout = setTimeout(function() {
console.log(`pollValue('${waiterId}', ${timeoutMillis}): timeout`);
reply(null);
}, timeoutMillis);
const kPollingIntervalMillis = 100;
interval = setInterval(function() {
replyIfSet(window.observedValueSlots[waiterId]);
}, kPollingIntervalMillis);
replyIfSet(window.observedValueSlots[waiterId]);
}
)";
std::string call = base::StringPrintf("pollValue(`%d`, %" PRId64 ")",
waiterId_, timeout.InMilliseconds());
content::EvalJsResult r =
content::EvalJs(execution_target_, kFunction + call,
content::EXECUTE_SCRIPT_USE_MANUAL_REPLY);
return !r.value.is_none() ? absl::make_optional(r.ExtractString())
: absl::nullopt;
}
private:
int waiterId_;
content::ToRenderFrameHost execution_target_;
};
// Registers observers for a value change of a field `id`. This listener fires
// on the first time *any* object whose ID is `id` changes its value to a
// non-empty string after the global `unblock_variable` has become true.
//
// It is particularly useful for detecting refills.
//
// For example, consider the following chain JavaScript statements:
//
// 1. window.unblock = undefined // or any other value that converts to false;
// 2. document.body.innerHTML += '<input id="id">';
// 3. document.getElementById('id').value = "foo";
// 4. document.getElementById('id').remove();
// 5. document.body.innerHTML += '<input id="id">';
// 6. document.getElementById('id').value = "foo";
// 7. window.unblock = true;
// 8. document.getElementById('id').value = "";
// 9. document.getElementById('id').value = "bar";
//
// Then `ListenForValueChange("id", "unblock", rfh).Wait(base::Seconds(5)) ==
// "bar"`. The ListenForValueChange() call happens any point before Event 9, and
// Event 9 happens no later than 5 seconds after that.
[[nodiscard]] ValueWaiter ListenForValueChange(
const std::string& id,
const absl::optional<std::string>& unblock_variable,
content::ToRenderFrameHost execution_target) {
const std::string kFunction = R"(
// This function observes the DOM for an attached form-control element `id`.
//
// On the first `change` event of such an element, it stores that element's
// value in an array `window[observedValueSlots]`.
//
// Returns the index of that value.
function listenForValueChange(id, unblockVariable) {
console.log(`listenForValueChange('${id}')`);
if (window.observedValueSlots === undefined)
window.observedValueSlots = [];
const waiterId = window.observedValueSlots.length;
window.observedValueSlots.push(undefined);
let observer = undefined;
function changeHandler() {
console.log(`listenForValueChange('${id}'): changeHandler()`);
// Since other handlers may manipulate the fields value or remove it
// from the DOM or replace it, we delay its execution.
setTimeout(function() {
console.log(`listenForValueChange('${id}'): changeHandler() timer`);
if (unblockVariable && window[unblockVariable] !== true) {
console.log(`listenForValueChange('${id}'): `+
`observed change, blocked by '${unblockVariable}'`);
return;
}
const e = document.getElementById(id);
if (e === null) {
console.log(`listenForValueChange('${id}'): element not found`);
return;
}
if (e.value === '') {
console.log(`listenForValueChange('${id}'): empty value`);
return;
}
console.log(`listenForValueChange('${id}'): storing in slot`);
window.observedValueSlots[waiterId] = e.value;
e.removeEventListener('change', changeHandler);
observer.disconnect();
}, 0);
}
// Observes the DOM to see if a new element `id` is added or some element
// changes its ID to `id`.
observer = new MutationObserver(function(mutations) {
const e = document.getElementById(id);
if (e !== null) {
console.log(`listenForValueChange('${id}'): some element has been `+
`attached or change`);
e.addEventListener('change', changeHandler);
}
});
observer.observe(document, {
attributes: true,
childList: true,
characterData: false,
subtree: true
});
const e = document.getElementById(id);
if (e !== null) {
console.log(`listenForValueChange('${id}'): element exists already`);
e.addEventListener('change', changeHandler);
}
return waiterId;
}
)";
std::string call =
base::StringPrintf("listenForValueChange(`%s`, `%s`)", id.c_str(),
unblock_variable.value_or("").c_str());
content::EvalJsResult r = content::EvalJs(execution_target, kFunction + call);
int waiterId = r.ExtractInt();
return ValueWaiter(waiterId, execution_target);
}
// Matcher for a FormData which checks that the submitted fields correspond
// to the name/value pairs in `expected`.
auto SubmittedValuesAre(
const std::map<std::u16string, std::u16string>& expected) {
auto get_submitted_values = [](const FormData& form) {
std::map<std::u16string, std::u16string> result;
for (const auto& field : form.fields) {
result[field.name] = field.value;
}
return result;
};
return ResultOf("get_submitted_values", get_submitted_values,
::testing::ContainerEq(expected));
}
} // namespace
// Test fixtures derive from this class. This class hierarchy allows test
// fixtures to have distinct list of test parameters.
//
// TODO(crbug.com/832707): Parametrize this class to ensure that all tests in
// this run with all possible valid combinations of
// features and field trials.
class AutofillInteractiveTestBase : public AutofillUiTest {
public:
AutofillInteractiveTestBase()
: https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {
// Disable AutofillPageLanguageDetection because due to the little text in
// the HTML files, the detected language is flaky (e.g., it often detects
// "fr" instead of "en").
feature_list_.InitWithFeatures(
/*enabled_features=*/
{blink::features::kAutofillShadowDOM},
/*disabled_features=*/{features::kAutofillPageLanguageDetection});
}
~AutofillInteractiveTestBase() override = default;
AutofillInteractiveTestBase(const AutofillInteractiveTestBase&) = delete;
AutofillInteractiveTestBase& operator=(const AutofillInteractiveTestBase&) =
delete;
std::vector<FieldValue> GetFormValues(
const ElementExpr& form = GetElementById("shipping")) {
return GetFieldValues(ElementExpr(*form + ".elements"), GetWebContents());
}
base::RepeatingClosure ExpectValues(
const std::vector<FieldValue>& expected_values,
const ElementExpr& form = GetElementById("shipping")) {
return base::BindRepeating(
[](AutofillInteractiveTestBase* self,
const std::vector<FieldValue>& expected_values,
const ElementExpr& form) {
EXPECT_THAT(self->GetFormValues(form), ValuesAre(expected_values));
},
this, expected_values, form);
}
content::EvalJsResult GetFieldValueById(const std::string& field_id) {
return GetFieldValue(GetElementById(field_id));
}
content::EvalJsResult GetFieldValue(ElementExpr e) {
return GetFieldValue(e, GetWebContents());
}
content::EvalJsResult GetFieldValue(
const ElementExpr& e,
content::ToRenderFrameHost execution_target) {
std::string script = base::StringPrintf("%s.value", e->c_str());
return content::EvalJs(execution_target, script);
}
void SetUp() override {
ASSERT_TRUE(embedded_test_server()->InitializeAndListen());
AutofillUiTest::SetUp();
}
void SetUpOnMainThread() override {
AutofillUiTest::SetUpOnMainThread();
https_server_.SetSSLConfig(net::EmbeddedTestServer::CERT_OK);
https_server_.ServeFilesFromSourceDirectory("chrome/test/data");
https_server_.RegisterRequestHandler(base::BindRepeating(
&AutofillInteractiveTestBase::HandleTestURL, base::Unretained(this)));
ASSERT_TRUE(https_server_.InitializeAndListen());
https_server_.StartAcceptingConnections();
controllable_http_response_ =
std::make_unique<net::test_server::ControllableHttpResponse>(
embedded_test_server(), "/mock_translate_script.js",
true /*relative_url_is_prefix*/);
// Ensure that |embedded_test_server()| serves both domains used below.
host_resolver()->AddRule("*", "127.0.0.1");
embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
&AutofillInteractiveTestBase::HandleTestURL, base::Unretained(this)));
embedded_test_server()->StartAcceptingConnections();
// By default, all SSL cert checks are valid. Can be overriden in tests if
// needed.
cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
}
void SetUpCommandLine(base::CommandLine* command_line) override {
AutofillUiTest::SetUpCommandLine(command_line);
cert_verifier_.SetUpCommandLine(command_line);
// Needed to allow input before commit on various builders.
command_line->AppendSwitch(blink::switches::kAllowPreCommitInput);
// TODO(crbug.com/1258185): Migrate to a better mechanism for testing around
// language detection.
command_line->AppendSwitch(switches::kOverrideLanguageDetection);
}
void SetUpInProcessBrowserTestFixture() override {
AutofillUiTest::SetUpInProcessBrowserTestFixture();
cert_verifier_.SetUpInProcessBrowserTestFixture();
}
void TearDownInProcessBrowserTestFixture() override {
cert_verifier_.TearDownInProcessBrowserTestFixture();
AutofillUiTest::TearDownInProcessBrowserTestFixture();
}
std::unique_ptr<net::test_server::HttpResponse> HandleTestURL(
const net::test_server::HttpRequest& request) {
if (!base::Contains(path_keyed_response_bodies_, request.relative_url)) {
return nullptr;
}
auto response = std::make_unique<net::test_server::BasicHttpResponse>();
response->set_code(net::HTTP_OK);
response->set_content_type("text/html;charset=utf-8");
response->set_content(path_keyed_response_bodies_[request.relative_url]);
return std::move(response);
}
translate::LanguageState& GetLanguageState() {
ChromeTranslateClient* client = ChromeTranslateClient::FromWebContents(
browser()->tab_strip_model()->GetActiveWebContents());
return *client->GetTranslateManager()->GetLanguageState();
}
// This is largely a copy of CheckForTranslateUI() from Translate's
// translate_language_browsertest.cc.
void NavigateToContentAndWaitForLanguageDetection(const char* content) {
ASSERT_TRUE(browser());
auto waiter = CreateTranslateWaiter(
browser()->tab_strip_model()->GetActiveWebContents(),
translate::TranslateWaiter::WaitEvent::kLanguageDetermined);
SetTestUrlResponse(content);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetTestUrl()));
waiter->Wait();
// Language detection sometimes fires early with an "und" (= undetermined)
// detected code.
size_t wait_counter = 0;
constexpr size_t kMaxWaits = 2;
while (GetLanguageState().source_language() == "und" ||
GetLanguageState().source_language().empty()) {
++wait_counter;
ASSERT_LE(wait_counter, kMaxWaits)
<< "Translate reported no/undetermined language " << wait_counter
<< " times";
CreateTranslateWaiter(
browser()->tab_strip_model()->GetActiveWebContents(),
translate::TranslateWaiter::WaitEvent::kLanguageDetermined)
->Wait();
}
const TranslateBubbleModel* model =
translate::test_utils::GetCurrentModel(browser());
ASSERT_NE(nullptr, model);
}
// This is largely a copy of Translate() from Translate's
// translate_language_browsertest.cc.
void Translate(const bool first_translate) {
auto waiter = CreateTranslateWaiter(
browser()->tab_strip_model()->GetActiveWebContents(),
translate::TranslateWaiter::WaitEvent::kPageTranslated);
EXPECT_EQ(
TranslateBubbleModel::VIEW_STATE_BEFORE_TRANSLATE,
translate::test_utils::GetCurrentModel(browser())->GetViewState());
translate::test_utils::PressTranslate(browser());
if (first_translate)
SimulateURLFetch();
waiter->Wait();
EXPECT_EQ(
TranslateBubbleModel::VIEW_STATE_AFTER_TRANSLATE,
translate::test_utils::GetCurrentModel(browser())->GetViewState());
}
void CreateTestProfile() {
AutofillProfile profile;
test::SetProfileInfo(
&profile, kDefaultAddressValues.first_name,
kDefaultAddressValues.middle_name, kDefaultAddressValues.last_name,
"red.swingline@initech.com", "Initech", kDefaultAddressValues.address1,
kDefaultAddressValues.address2, kDefaultAddressValues.city,
kDefaultAddressValues.state, kDefaultAddressValues.zip,
kDefaultAddressValues.country, kDefaultAddressValues.phone);
profile.set_use_count(9999999); // We want this to be the first profile.
AddTestProfile(browser()->profile(), profile);
}
void CreateSecondTestProfile() {
AutofillProfile profile;
test::SetProfileInfo(&profile, "Alice", "M.", "Wonderland",
"alice@wonderland.com", "Magic", "333 Cat Queen St.",
"Rooftop", "Liliput", "CA", "10003", "US",
"15166900292");
AddTestProfile(browser()->profile(), profile);
}
void CreateTestCreditCart() {
CreditCard card;
test::SetCreditCardInfo(&card, "Milton Waddams", "4111111111111111", "09",
"2999", "");
AddTestCreditCard(browser()->profile(), card);
}
void SimulateURLFetch() {
std::string script = R"(
var google = {};
google.translate = (function() {
return {
TranslateService: function() {
return {
isAvailable : function() {
return true;
},
restore : function() {
return;
},
getDetectedLanguage : function() {
return "ja";
},
translatePage : function(sourceLang, targetLang,
onTranslateProgress) {
document.getElementsByTagName("body")[0].innerHTML = `)" +
std::string(kTestShippingFormString) + R"(`;
onTranslateProgress(100, true, false);
}
};
}
};
})();
cr.googleTranslate.onTranslateElementLoad(); )";
controllable_http_response_->WaitForRequest();
controllable_http_response_->Send(
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/javascript\r\n"
"\r\n");
controllable_http_response_->Send(script);
controllable_http_response_->Done();
}
// Make a pointless round trip to the renderer, giving the popup a chance to
// show if it's going to. If it does show, an assert in
// BrowserAutofillManagerTestDelegateImpl will trigger.
void MakeSurePopupDoesntAppear() {
int unused;
ASSERT_TRUE(content::ExecuteScriptAndExtractInt(
GetWebContents(), "domAutomationController.send(42)", &unused));
}
void FillElementWithValue(const std::string& element_id,
const std::string& value) {
// Sends "|element_id|:|value|" to |msg_queue| if the |element_id|'s
// value has changed to |value|.
std::string script = base::StringPrintf(
R"( (function() {
const element_id = '%s';
const value = '%s';
const field = document.getElementById(element_id);
const listener = function() {
if (field.value === value) {
field.removeEventListener('input', listener);
domAutomationController.send(element_id +':'+ field.value);
}
};
field.addEventListener('input', listener, false);
return 'done';
})(); )",
element_id.c_str(), value.c_str());
ASSERT_TRUE(content::ExecJs(GetWebContents(), script));
content::DOMMessageQueue msg_queue(GetWebContents());
for (char16_t character : value) {
ui::DomKey dom_key = ui::DomKey::FromCharacter(character);
const ui::PrintableCodeEntry* code_entry = base::ranges::find_if(
ui::kPrintableCodeMap,
[character](const ui::PrintableCodeEntry& entry) {
return entry.character[0] == character ||
entry.character[1] == character;
});
ASSERT_TRUE(code_entry != std::end(ui::kPrintableCodeMap));
bool shift = code_entry->character[1] == character;
ui::DomCode dom_code = code_entry->dom_code;
content::SimulateKeyPress(GetWebContents(), dom_key, dom_code,
ui::DomCodeToUsLayoutKeyboardCode(dom_code),
false, shift, false, false);
}
std::string reply;
ASSERT_TRUE(msg_queue.WaitForMessage(&reply));
ASSERT_EQ("\"" + element_id + ":" + value + "\"", reply);
}
void DeleteElementValue(const ElementExpr& field) {
std::string script = base::StringPrintf("%s.value = '';", field->c_str());
ASSERT_TRUE(content::ExecJs(GetWebContents(), script));
ASSERT_EQ("", GetFieldValue(field));
}
GURL GetTestUrl() const { return https_server_.GetURL(kTestUrlPath); }
void SetTestUrlResponse(std::string content) {
SetResponseForUrlPath(kTestUrlPath, std::move(content));
}
void SetResponseForUrlPath(std::string path, std::string content) {
path_keyed_response_bodies_[std::move(path)] = std::move(content);
}
net::EmbeddedTestServer* https_server() { return &https_server_; }
static const char kTestUrlPath[];
base::HistogramTester& histogram_tester() { return histogram_tester_; }
private:
net::EmbeddedTestServer https_server_;
// Similar to net::MockCertVerifier, but also updates the CertVerifier
// used by the NetworkService.
content::ContentMockCertVerifier cert_verifier_;
// KeyPressEventCallback that serves as a sink to ensure that every key press
// event the tests create and have the WebContents forward is handled by some
// key press event callback. It is necessary to have this sinkbecause if no
// key press event callback handles the event (at least on Mac), a DCHECK
// ends up going off that the |event| doesn't have an |os_event| associated
// with it.
content::RenderWidgetHost::KeyPressEventCallback key_press_event_sink_;
std::unique_ptr<net::test_server::ControllableHttpResponse>
controllable_http_response_;
// A map of relative paths to content that shall be served with an HTTP_OK
// response. If the map contains no entry, the request falls through to the
// serving from disk.
std::map<std::string, std::string> path_keyed_response_bodies_;
base::test::ScopedFeatureList feature_list_;
base::HistogramTester histogram_tester_;
};
const char AutofillInteractiveTestBase::kTestUrlPath[] =
"/internal/test_url_path";
class AutofillInteractiveTest : public AutofillInteractiveTestBase {
protected:
AutofillInteractiveTest() = default;
~AutofillInteractiveTest() override = default;
void SetUpCommandLine(base::CommandLine* command_line) override {
AutofillInteractiveTestBase::SetUpCommandLine(command_line);
command_line->AppendSwitchASCII(
translate::switches::kTranslateScriptURL,
embedded_test_server()->GetURL("/mock_translate_script.js").spec());
}
};
class AutofillInteractiveTestWithHistogramTester
: public AutofillInteractiveTest {
public:
AutofillInteractiveTestWithHistogramTester() {
feature_list_.InitWithFeatureState(
features::test::kAutofillServerCommunication, true);
}
void SetUp() override {
url_loader_interceptor_ = std::make_unique<URLLoaderInterceptor>(
base::BindRepeating([](URLLoaderInterceptor::RequestParams* params) {
// Only allow requests to be loaded that are necessary for the test.
// This allows a histogram to test properties of some specific
// requests.
std::vector<std::string> allowlist = {
"/internal/test_url_path", "https://clients1.google.com/tbproxy",
"https://content-autofill.googleapis.com/"};
// Intercept if not allow-listed.
return base::ranges::all_of(allowlist, [&params](const auto& s) {
return params->url_request.url.spec().find(s) == std::string::npos;
});
}));
AutofillInteractiveTest::SetUp();
}
void TearDownOnMainThread() override {
url_loader_interceptor_.reset();
AutofillInteractiveTest::TearDownOnMainThread();
}
void SetUpCommandLine(base::CommandLine* command_line) override {
AutofillInteractiveTest::SetUpCommandLine(command_line);
// Prevents proxy.pac requests.
command_line->AppendSwitch(switches::kNoProxyServer);
}
private:
std::unique_ptr<URLLoaderInterceptor> url_loader_interceptor_;
base::test::ScopedFeatureList feature_list_;
};
// Test the basic form-fill flow.
// TODO(https://crbug.com/1045709): Check back if flakiness is fixed now.
IN_PROC_BROWSER_TEST_F(AutofillInteractiveTest, BasicFormFill) {
CreateTestProfile();
SetTestUrlResponse(kTestShippingFormString);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetTestUrl()));
ASSERT_TRUE(AutofillFlow(GetElementById("firstname"), this,
{.show_method = ShowMethod::ByChar('M'),
.after_select = ExpectValues(MergeValue(
kEmptyAddress, {"firstname", "M"}))}));
EXPECT_THAT(GetFormValues(), ValuesAre(kDefaultAddress));
}
IN_PROC_BROWSER_TEST_F(AutofillInteractiveTest, BasicClear) {
CreateTestProfile();
SetTestUrlResponse(kTestShippingFormString);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetTestUrl()));
ASSERT_TRUE(AutofillFlow(GetElementById("firstname"), this,
{.show_method = ShowMethod::ByChar('M'),
.after_select = ExpectValues(MergeValue(
kEmptyAddress, {"firstname", "M"}))}));
EXPECT_THAT(GetFormValues(), ValuesAre(kDefaultAddress));
ASSERT_TRUE(
AutofillFlow(GetElementById("firstname"), this, {.target_index = 1}));
EXPECT_THAT(GetFormValues(), ValuesAre(kEmptyAddress));
}
IN_PROC_BROWSER_TEST_F(AutofillInteractiveTest, ClearTwoSection) {
static const char kTestBillingFormString[] =
R"( An example of a billing address form.
<form action="https://www.example.com/" method="POST" id="billing">
<label for="firstname_billing">First name:</label>
<input type="text" id="firstname_billing"><br>
<label for="lastname_billing">Last name:</label>
<input type="text" id="lastname_billing"><br>
<label for="address1_billing">Address line 1:</label>
<input type="text" id="address1_billing"><br>
<label for="address2_billing">Address line 2:</label>
<input type="text" id="address2_billing"><br>
<label for="city_billing">City:</label>
<input type="text" id="city_billing"><br>
<label for="state_billing">State:</label>
<select id="state_billing">
<option value="" selected="yes">--</option>
<option value="CA">California</option>
<option value="TX">Texas</option>
</select><br>
<label for="zip_billing">ZIP code:</label>
<input type="text" id="zip_billing"><br>
<label for="country_billing">Country:</label>
<select id="country_billing">
<option value="" selected="yes">--</option>
<option value="CA">Canada</option>
<option value="US">United States</option>
</select><br>
<label for="phone_billing">Phone number:</label>
<input type="text" id="phone_billing"><br>
</form> )";
CreateTestProfile();
SetTestUrlResponse(
base::StrCat({kTestShippingFormString, kTestBillingFormString}));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetTestUrl()));
// Fill shipping form.
ASSERT_TRUE(AutofillFlow(GetElementById("firstname"), this,
{.show_method = ShowMethod::ByChar('M'),
.after_select = ExpectValues(MergeValue(
kEmptyAddress, {"firstname", "M"}))}));
EXPECT_THAT(GetFormValues(GetElementById("shipping")),
ValuesAre(kDefaultAddress));
EXPECT_THAT(GetFormValues(GetElementById("billing")),
ValuesAre(kEmptyAddress));
// Fill billing form.
ASSERT_TRUE(AutofillFlow(GetElementById("firstname_billing"), this));
EXPECT_THAT(GetFormValues(GetElementById("billing")),
ValuesAre(kDefaultAddress));
EXPECT_THAT(GetFormValues(GetElementById("shipping")),
ValuesAre(kDefaultAddress));
// Clear billing form.
ASSERT_TRUE(AutofillFlow(GetElementById("firstname_billing"), this,
{.target_index = 1}));
EXPECT_THAT(GetFormValues(GetElementById("shipping")),
ValuesAre(kDefaultAddress));
EXPECT_THAT(GetFormValues(GetElementById("billing")),
ValuesAre(kEmptyAddress));
}
IN_PROC_BROWSER_TEST_F(AutofillInteractiveTest, ModifyTextFieldAndFill) {
CreateTestProfile();
SetTestUrlResponse(kTestShippingFormString);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetTestUrl()));
// Modify a field.
ASSERT_TRUE(FocusField(GetElementById("city"), GetWebContents()));
FillElementWithValue("city", "Montreal");
ASSERT_TRUE(AutofillFlow(GetElementById("firstname"), this));
EXPECT_THAT(GetFormValues(),
ValuesAre(MergeValue(kDefaultAddress, {"city", "Montreal"})));
}
// Test that autofill doesn't refill a select field initially modified by the
// user.
IN_PROC_BROWSER_TEST_F(AutofillInteractiveTest, ModifySelectFieldAndFill) {
CreateTestProfile();
SetTestUrlResponse(kTestShippingFormString);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetTestUrl()));
// Modify a field.
ASSERT_TRUE(FocusField(GetElementById("state"), GetWebContents()));
ASSERT_NE(strcmp(kDefaultAddressValues.state_short, "CA"), 0);
FillElementWithValue("state", "CA");
ASSERT_TRUE(AutofillFlow(GetElementById("firstname"), this));
EXPECT_THAT(GetFormValues(),
ValuesAre(MergeValue(kDefaultAddress, {"state", "CA"})));
}
// Test that autofill works when the website prefills the form when
// |kAutofillPreventOverridingPrefilledValues| is not enabled, otherwise, the
// prefilled field values are not overridden.
IN_PROC_BROWSER_TEST_F(AutofillInteractiveTest, PrefillFormAndFill) {
const char kPrefillScript[] =
R"( <script>
document.getElementById('firstname').value = 'Seb';
document.getElementById('lastname').value = 'Bell';
document.getElementById('address1').value = '3243 Notre-Dame Ouest';
document.getElementById('address2').value = 'apt 843';
document.getElementById('city').value = 'Montreal';
document.getElementById('zip').value = 'H5D 4D3';
document.getElementById('phone').value = '15142223344';
</script>)";
CreateTestProfile();
SetTestUrlResponse(base::StrCat({kTestShippingFormString, kPrefillScript}));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetTestUrl()));
// We need to delete the prefilled value and then trigger the autofill.
auto Delete = [this] { DeleteElementValue(GetElementById("firstname")); };
ASSERT_TRUE(
AutofillFlow(GetElementById("firstname"), this,
{.after_focus = base::BindLambdaForTesting(Delete)}));
if (base::FeatureList::IsEnabled(
features::kAutofillPreventOverridingPrefilledValues)) {
EXPECT_EQ("Milton", GetFieldValueById("firstname"));
EXPECT_EQ("Bell", GetFieldValueById("lastname"));
EXPECT_EQ("3243 Notre-Dame Ouest", GetFieldValueById("address1"));
EXPECT_EQ("apt 843", GetFieldValueById("address2"));
EXPECT_EQ("Montreal", GetFieldValueById("city"));
EXPECT_EQ("H5D 4D3", GetFieldValueById("zip"));
EXPECT_EQ("15142223344", GetFieldValueById("phone"));
} else {
EXPECT_THAT(GetFormValues(), ValuesAre(kDefaultAddress));
}
}
// Test that autofill doesn't refill a field modified by the user.
IN_PROC_BROWSER_TEST_F(AutofillInteractiveTest,
FillChangeSecondFieldRefillAndClearFirstFill) {
CreateTestProfile();
SetTestUrlResponse(kTestShippingFormString);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetTestUrl()));
ASSERT_TRUE(AutofillFlow(GetElementById("firstname"), this,
{.show_method = ShowMethod::ByChar('M'),
.after_select = ExpectValues(MergeValue(
kEmptyAddress, {"firstname", "M"}))}));
EXPECT_THAT(GetFormValues(), ValuesAre(kDefaultAddress));
// Change the last name.
ASSERT_TRUE(FocusField(GetElementById("lastname"), GetWebContents()));
SendKeyToPageAndWait(ui::DomKey::BACKSPACE,
{ObservedUiEvents::kSuggestionShown});
SendKeyToPageAndWait(ui::DomKey::BACKSPACE,
{ObservedUiEvents::kSuggestionShown});
EXPECT_THAT(GetFormValues(),
ValuesAre(MergeValue(kDefaultAddress, {"lastname", "Wadda"})));
// Fill again by focusing on the first field.
ASSERT_TRUE(AutofillFlow(GetElementById("firstname"), this));
EXPECT_THAT(GetFormValues(),
ValuesAre(MergeValue(kDefaultAddress, {"lastname", "Wadda"})));
// Clear everything except last name by selecting 'clear' on the first field.
ASSERT_TRUE(
AutofillFlow(GetElementById("firstname"), this, {.target_index = 1}));
EXPECT_THAT(GetFormValues(),
ValuesAre(MergeValue(kEmptyAddress, {"lastname", "Wadda"})));
}
// Test that multiple autofillings work.
IN_PROC_BROWSER_TEST_F(AutofillInteractiveTest,
FillChangeSecondFieldRefillAndClearSecondField) {
CreateTestProfile();
SetTestUrlResponse(kTestShippingFormString);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetTestUrl()));
ASSERT_TRUE(AutofillFlow(GetElementById("firstname"), this,
{.show_method = ShowMethod::ByChar('M'),
.after_select = ExpectValues(MergeValue(
kEmptyAddress, {"firstname", "M"}))}));
EXPECT_THAT(GetFormValues(), ValuesAre(kDefaultAddress));
// Change the last name.
ASSERT_TRUE(FocusField(GetElementById("lastname"), GetWebContents()));
SendKeyToPageAndWait(ui::DomKey::BACKSPACE,
{ObservedUiEvents::kSuggestionShown});
SendKeyToPageAndWait(ui::DomKey::BACKSPACE,
{ObservedUiEvents::kSuggestionShown});
EXPECT_THAT(GetFormValues(),
ValuesAre(MergeValue(kDefaultAddress, {"lastname", "Wadda"})));
ASSERT_TRUE(AutofillFlow(GetElementById("firstname"), this,
{.do_focus = false,
.do_show = false,
.show_method = ShowMethod::ByChar('M')}));
EXPECT_THAT(GetFormValues(), ValuesAre(kDefaultAddress));
ASSERT_TRUE(
AutofillFlow(GetElementById("firstname"), this, {.target_index = 1}));
EXPECT_THAT(GetFormValues(), ValuesAre(kEmptyAddress));
}
// Test that multiple autofillings work.
IN_PROC_BROWSER_TEST_F(AutofillInteractiveTest,
FillChangeSecondFieldRefillSecondFieldClearFirst) {
CreateTestProfile();
SetTestUrlResponse(kTestShippingFormString);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetTestUrl()));
ASSERT_TRUE(AutofillFlow(GetElementById("firstname"), this,
{.show_method = ShowMethod::ByChar('M'),
.after_select = ExpectValues(MergeValue(
kEmptyAddress, {"firstname", "M"}))}));
EXPECT_THAT(GetFormValues(), ValuesAre(kDefaultAddress));
// Change the last name.
ASSERT_TRUE(FocusField(GetElementById("lastname"), GetWebContents()));
SendKeyToPageAndWait(ui::DomKey::BACKSPACE,
{ObservedUiEvents::kSuggestionShown});
SendKeyToPageAndWait(ui::DomKey::BACKSPACE,
{ObservedUiEvents::kSuggestionShown});
EXPECT_THAT(GetFormValues(),
ValuesAre(MergeValue(kDefaultAddress, {"lastname", "Wadda"})));
ASSERT_TRUE(AutofillFlow(GetElementById("lastname"), this));
EXPECT_THAT(GetFormValues(), ValuesAre(kDefaultAddress));
ASSERT_TRUE(
AutofillFlow(GetElementById("firstname"), this, {.target_index = 1}));
EXPECT_THAT(GetFormValues(), ValuesAre(kEmptyAddress));
}
// Test that multiple autofillings work.
IN_PROC_BROWSER_TEST_F(AutofillInteractiveTest,
FillThenFillSomeWithAnotherProfileThenClear) {
CreateTestProfile();
CreateSecondTestProfile();
SetTestUrlResponse(kTestShippingFormString);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetTestUrl()));
ASSERT_TRUE(AutofillFlow(GetElementById("firstname"), this,
{.show_method = ShowMethod::ByChar('M'),
.after_select = ExpectValues(MergeValue(
kEmptyAddress, {"firstname", "M"}))}));
EXPECT_THAT(GetFormValues(), ValuesAre(kDefaultAddress));
// Delete some fields.
ASSERT_TRUE(FocusField(GetElementById("city"), GetWebContents()));
DeleteElementValue(GetElementById("city"));
ASSERT_TRUE(AutofillFlow(
GetElementById("address1"), this,
{.target_index = 1, .after_focus = base::BindLambdaForTesting([&]() {
DeleteElementValue(GetElementById("address1"));
})}));
// Address line 1 and city from the second profile.
EXPECT_THAT(
GetFormValues(),
ValuesAre(MergeValues(kDefaultAddress, {{"address1", "333 Cat Queen St."},
{"city", "Liliput"}})));
ASSERT_TRUE(
AutofillFlow(GetElementById("firstname"), this, {.target_index = 1}));
EXPECT_THAT(GetFormValues(), ValuesAre(kEmptyAddress));
}
// Test that form filling can be initiated by pressing the down arrow.
IN_PROC_BROWSER_TEST_F(AutofillInteractiveTest, AutofillViaDownArrow) {
CreateTestProfile();
SetTestUrlResponse(kTestShippingFormString);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetTestUrl()));
// Focus a fillable field.
ASSERT_TRUE(AutofillFlow(GetElementById("firstname"), this));
// The form should be filled.
EXPECT_THAT(GetFormValues(), ValuesAre(kDefaultAddress));
}
IN_PROC_BROWSER_TEST_F(AutofillInteractiveTest, AutofillSelectViaTab) {
CreateTestProfile();
SetTestUrlResponse(kTestShippingFormString);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetTestUrl()));
// Focus a fillable field.
ASSERT_TRUE(AutofillFlow(GetElementById("firstname"), this));
// The form should be filled.
EXPECT_THAT(GetFormValues(), ValuesAre(kDefaultAddress));
}
IN_PROC_BROWSER_TEST_F(AutofillInteractiveTest, AutofillViaClick) {
CreateTestProfile();
SetTestUrlResponse(kTestShippingFormString);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetTestUrl()));
ASSERT_TRUE(AutofillFlow(GetElementById("firstname"), this,
{.show_method = ShowMethod::ByClick(),
.execution_target = GetWebContents()}));
EXPECT_THAT(GetFormValues(), ValuesAre(kDefaultAddress));
}
// Makes sure that the first click does or does not activate the autofill popup
// on the initial click within a fillable field.
IN_PROC_BROWSER_TEST_F(AutofillInteractiveTest, Click) {
CreateTestProfile();
SetTestUrlResponse(kTestShippingFormString);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetTestUrl()));
ASSERT_TRUE(AutofillFlow(GetElementById("firstname"), this,
{.do_focus = false,
.show_method = ShowMethod::ByClick(),
.execution_target = GetWebContents()}));
EXPECT_THAT(GetFormValues(), ValuesAre(kDefaultAddress));
}
// Makes sure that clicking outside the focused field doesn't activate
// the popup.
IN_PROC_BROWSER_TEST_F(AutofillInteractiveTest, DontAutofillForOutsideClick) {
static const char kDisabledButton[] =
R"(<button disabled id='disabled-button'>Cant click this</button>)";
CreateTestProfile();
SetTestUrlResponse(base::StrCat({kTestShippingFormString, kDisabledButton}));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetTestUrl()));
// Clicking a disabled button will generate a mouse event but focus doesn't
// change. This tests that autofill can handle a mouse event outside a focused
// input *without* showing the popup.
ASSERT_FALSE(AutofillFlow(GetElementById("disabled-button"), this,
{.do_focus = false,
.do_select = false,
.do_accept = false,
.show_method = ShowMethod::ByClick(),
.execution_target = GetWebContents()}));
ASSERT_TRUE(AutofillFlow(GetElementById("firstname"), this,
{.do_focus = false,
.show_method = ShowMethod::ByClick(),
.execution_target = GetWebContents()}));
}
// Makes sure that clicking a field while there is no enough height in the
// content area for at least one suggestion, won't show the autofill popup. This
// is a regression test for crbug.com/1108181
IN_PROC_BROWSER_TEST_F(AutofillInteractiveTest,
DontAutofillShowPopupWhenNoEnoughHeightInContentArea) {
// This firstname field starts at y=-100px and has a height of 5120px. There
// is no enough space to show at least one row of the autofill popup and hence
// the autofill shouldn't be shown.
static const char kTestFormWithLargeInputField[] =
R"(<form action="https://www.example.com/" method="POST">
<label for="firstname">First name:</label>
<input type="text" id="firstname" style="position:fixed;
top:-100px;height:5120px"><br>
<label for="lastname">Last name:</label>
<input type="text" id="lastname"><br>
<label for="city">City:</label>
<input type="text" id="city"><br>
</form>)";
CreateTestProfile();
SetTestUrlResponse(kTestFormWithLargeInputField);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetTestUrl()));
ASSERT_FALSE(AutofillFlow(GetElementById("firstname"), this,
{.do_select = false,
.do_accept = false,
.show_method = ShowMethod::ByClick(),
// Since failure is expected, no need to retry
// showing the Autofill popup too often.
.max_show_tries = 2,
.execution_target = GetWebContents()}));
}
// Test that a field is still autofillable after the previously autofilled
// value is deleted.
IN_PROC_BROWSER_TEST_F(AutofillInteractiveTest, OnDeleteValueAfterAutofill) {
CreateTestProfile();
SetTestUrlResponse(kTestShippingFormString);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetTestUrl()));
ASSERT_TRUE(AutofillFlow(GetElementById("firstname"), this,
{.show_method = ShowMethod::ByChar('M')}));
EXPECT_THAT(GetFormValues(), ValuesAre(kDefaultAddress));
// Delete the value of a filled field.
DeleteElementValue(GetElementById("firstname"));
ASSERT_TRUE(AutofillFlow(GetElementById("firstname"), this,
{.show_method = ShowMethod::ByChar('M')}));
EXPECT_EQ("Milton", GetFieldValue(GetElementById("firstname")));
}
// Test that an input field is not rendered with the blue autofilled
// background color when choosing an option from the datalist suggestion list.
IN_PROC_BROWSER_TEST_F(AutofillInteractiveTest, OnSelectOptionFromDatalist) {
static const char kTestForm[] =
R"( <p>The text is some page content to paint</p>
<form action="https://www.example.com/" method="POST">
<input list="dl" type="search" id="firstname"><br>
<datalist id="dl">
<option value="Adam"></option>
<option value="Bob"></option>
<option value="Carl"></option>
</datalist>
</form> )";
SetTestUrlResponse(kTestForm);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetTestUrl()));
auto GetBackgroundColor = [this](const ElementExpr& id) {
std::string script = base::StringPrintf(
"document.defaultView.getComputedStyle(%s).backgroundColor",
id->c_str());
return content::EvalJs(GetWebContents(), script).ExtractString();
};
std::string orginalcolor = GetBackgroundColor(GetElementById("firstname"));
ASSERT_TRUE(AutofillFlow(GetElementById("firstname"), this,
{.num_profile_suggestions = 0, .target_index = 1}));
EXPECT_EQ("Bob", GetFieldValueById("firstname"));
EXPECT_EQ(GetBackgroundColor(GetElementById("firstname")), orginalcolor);
}
// Test that an <input> field with a <datalist> has a working drop down even if
// it was dynamically changed to <input type="password"> temporarily. This is a
// regression test for crbug.com/918351.
IN_PROC_BROWSER_TEST_F(
AutofillInteractiveTest,
OnSelectOptionFromDatalistTurningToPasswordFieldAndBack) {
static const char kTestForm[] =
R"( <p>The text is some page content to paint</p>
<form action="https://www.example.com/" method="POST">
<input list="dl" type="search" id="firstname"><br>
<datalist id="dl">
<option value="Adam"></option>
<option value="Bob"></option>
<option value="Carl"></option>
</datalist>
</form> )";
SetTestUrlResponse(kTestForm);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetTestUrl()));
ASSERT_TRUE(content::ExecuteScript(
GetWebContents(),
"document.getElementById('firstname').type = 'password';"));
// At this point, the IsPasswordFieldForAutofill() function returns true and
// will continue to return true for the field, even when the type is changed
// back to 'search'.
ASSERT_TRUE(content::ExecuteScript(
GetWebContents(),
"document.getElementById('firstname').type = 'search';"));
// Regression test for crbug.com/918351 whether the datalist becomes available
// again.
ASSERT_TRUE(AutofillFlow(GetElementById("firstname"), this,
{.num_profile_suggestions = 0, .target_index = 1}));
// Pressing the down arrow preselects the first item. Pressing it again
// selects the second item.
EXPECT_EQ("Bob", GetFieldValueById("firstname"));
}
// Test that a JavaScript oninput event is fired after auto-filling a form.
IN_PROC_BROWSER_TEST_F(AutofillInteractiveTest, OnInputAfterAutofill) {
static const char kOnInputScript[] =
R"( <script>
focused_fired = false;
unfocused_fired = false;
changed_select_fired = false;
unchanged_select_fired = false;
document.getElementById('firstname').oninput = function() {
focused_fired = true;
};
document.getElementById('lastname').oninput = function() {
unfocused_fired = true;
};
document.getElementById('state').oninput = function() {
changed_select_fired = true;
};
document.getElementById('country').oninput = function() {
unchanged_select_fired = true;
};
document.getElementById('country').value = 'US';
</script> )";
CreateTestProfile();
SetTestUrlResponse(base::StrCat({kTestShippingFormString, kOnInputScript}));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetTestUrl()));
ASSERT_TRUE(AutofillFlow(GetElementById("firstname"), this,
{.show_method = ShowMethod::ByChar('M')}));
EXPECT_THAT(GetFormValues(), ValuesAre(kDefaultAddress));
bool focused_fired = false;
bool unfocused_fired = false;
bool changed_select_fired = false;
bool unchanged_select_fired = false;
ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
GetWebContents(), "domAutomationController.send(focused_fired);",
&focused_fired));
ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
GetWebContents(), "domAutomationController.send(unfocused_fired);",
&unfocused_fired));
ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
GetWebContents(), "domAutomationController.send(changed_select_fired);",
&changed_select_fired));
ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
GetWebContents(), "domAutomationController.send(unchanged_select_fired);",
&unchanged_select_fired));
EXPECT_TRUE(focused_fired);
EXPECT_TRUE(unfocused_fired);
EXPECT_TRUE(changed_select_fired);
EXPECT_FALSE(unchanged_select_fired);
}
// Test that a JavaScript onchange event is fired after auto-filling a form.
IN_PROC_BROWSER_TEST_F(AutofillInteractiveTest, OnChangeAfterAutofill) {
static const char kOnChangeScript[] =
R"( <script>
focused_fired = false;
unfocused_fired = false;
changed_select_fired = false;
unchanged_select_fired = false;
document.getElementById('firstname').onchange = function() {
focused_fired = true;
};
document.getElementById('lastname').onchange = function() {
unfocused_fired = true;
};
document.getElementById('state').onchange = function() {
changed_select_fired = true;
};
document.getElementById('country').onchange = function() {
unchanged_select_fired = true;
};
document.getElementById('country').value = 'US';
</script> )";
CreateTestProfile();
SetTestUrlResponse(base::StrCat({kTestShippingFormString, kOnChangeScript}));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetTestUrl()));
ASSERT_TRUE(AutofillFlow(GetElementById("firstname"), this,
{.show_method = ShowMethod::ByChar('M')}));
// The form should be filled.
EXPECT_THAT(GetFormValues(), ValuesAre(kDefaultAddress));
bool focused_fired = false;
bool unfocused_fired = false;
bool changed_select_fired = false;
bool unchanged_select_fired = false;
ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
GetWebContents(), "domAutomationController.send(focused_fired);",
&focused_fired));
ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
GetWebContents(), "domAutomationController.send(unfocused_fired);",
&unfocused_fired));
ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
GetWebContents(), "domAutomationController.send(changed_select_fired);",
&changed_select_fired));
ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
GetWebContents(), "domAutomationController.send(unchanged_select_fired);",
&unchanged_select_fired));
EXPECT_TRUE(focused_fired);
EXPECT_TRUE(unfocused_fired);
EXPECT_TRUE(changed_select_fired);
EXPECT_FALSE(unchanged_select_fired);
}
IN_PROC_BROWSER_TEST_F(AutofillInteractiveTest, InputFiresBeforeChange) {
static const char kInputFiresBeforeChangeScript[] =
R"(<script>
inputElementEvents = [];
function recordInputElementEvent(e) {
if (e.target.tagName != 'INPUT') throw 'only <input> tags allowed';
inputElementEvents.push(e.type);
}
selectElementEvents = [];
function recordSelectElementEvent(e) {
if (e.target.tagName != 'SELECT') throw 'only <select> tags allowed';
selectElementEvents.push(e.type);
}
document.getElementById('lastname').oninput = recordInputElementEvent;
document.getElementById('lastname').onchange = recordInputElementEvent;
document.getElementById('country').oninput = recordSelectElementEvent;
document.getElementById('country').onchange = recordSelectElementEvent;
</script>)";
CreateTestProfile();
SetTestUrlResponse(
base::StrCat({kTestShippingFormString, kInputFiresBeforeChangeScript}));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetTestUrl()));
ASSERT_TRUE(AutofillFlow(GetElementById("firstname"), this,
{.show_method = ShowMethod::ByChar('M'),
.after_select = ExpectValues(MergeValue(
kEmptyAddress, {"firstname", "M"}))}));
EXPECT_THAT(GetFormValues(), ValuesAre(kDefaultAddress));
int num_input_element_events = -1;
ASSERT_TRUE(content::ExecuteScriptAndExtractInt(
GetWebContents(),
"domAutomationController.send(inputElementEvents.length);",
&num_input_element_events));
EXPECT_EQ(2, num_input_element_events);
std::vector<std::string> input_element_events;
input_element_events.resize(2);
ASSERT_TRUE(content::ExecuteScriptAndExtractString(
GetWebContents(), "domAutomationController.send(inputElementEvents[0]);",
&input_element_events[0]));
ASSERT_TRUE(content::ExecuteScriptAndExtractString(
GetWebContents(), "domAutomationController.send(inputElementEvents[1]);",
&input_element_events[1]));
EXPECT_EQ("input", input_element_events[0]);
EXPECT_EQ("change", input_element_events[1]);
int num_select_element_events = -1;
ASSERT_TRUE(content::ExecuteScriptAndExtractInt(
GetWebContents(),
"domAutomationController.send(selectElementEvents.length);",
&num_select_element_events));
EXPECT_EQ(2, num_select_element_events);
std::vector<std::string> select_element_events;
select_element_events.resize(2);
ASSERT_TRUE(content::ExecuteScriptAndExtractString(
GetWebContents(), "domAutomationController.send(selectElementEvents[0]);",
&select_element_events[0]));
ASSERT_TRUE(content::ExecuteScriptAndExtractString(
GetWebContents(), "domAutomationController.send(selectElementEvents[1]);",
&select_element_events[1]));
EXPECT_EQ("input", select_element_events[0]);
EXPECT_EQ("change", select_element_events[1]);
}
// Test that we can autofill forms distinguished only by their |id| attribute.
IN_PROC_BROWSER_TEST_F(AutofillInteractiveTest,
AutofillFormsDistinguishedById) {
static const char kScript[] =
R"( <script>
var mainForm = document.forms[0];
mainForm.id = 'mainForm';
var newForm = document.createElement('form');
newForm.action = mainForm.action;
newForm.method = mainForm.method;
newForm.id = 'newForm';
mainForm.parentNode.insertBefore(newForm, mainForm);
</script> )";
CreateTestProfile();
SetTestUrlResponse(base::StrCat({kTestShippingFormString, kScript}));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetTestUrl()));
ASSERT_TRUE(AutofillFlow(GetElementById("firstname"), this,
{.show_method = ShowMethod::ByChar('M'),
.after_select = ExpectValues(
MergeValue(kEmptyAddress, {"firstname", "M"}),
GetElementById("mainForm"))}));
EXPECT_THAT(GetFormValues(GetElementById("mainForm")),
ValuesAre(kDefaultAddress));
}
// Test that we properly autofill forms with repeated fields.
// In the wild, the repeated fields are typically either email fields
// (duplicated for "confirmation"); or variants that are hot-swapped via
// JavaScript, with only one actually visible at any given time.
IN_PROC_BROWSER_TEST_F(AutofillInteractiveTest, AutofillFormWithRepeatedField) {
static const char kForm[] =
R"( <form action="https://www.example.com/" method="POST">
<label for="firstname">First name:</label>
<input type="text" id="firstname"><br>
<label for="lastname">Last name:</label>
<input type="text" id="lastname"><br>
<label for="address1">Address line 1:</label>
<input type="text" id="address1"><br>
<label for="address2">Address line 2:</label>
<input type="text" id="address2"><br>
<label for="city">City:</label>
<input type="text" id="city"><br>
<label for="state">State:</label>
<select id="state">
<option value="" selected="yes">--</option>
<option value="CA">California</option>
<option value="TX">Texas</option>
</select><br>
<label for="state_freeform" style="display:none">State:</label>
<input type="text" id="state_freeform" style="display:none"><br>
<label for="zip">ZIP code:</label>
<input type="text" id="zip"><br>
<label for="country">Country:</label>
<select id="country">
<option value="" selected="yes">--</option>
<option value="CA">Canada</option>
<option value="US">United States</option>
</select><br>
<label for="phone">Phone number:</label>
<input type="text" id="phone"><br>
</form> )";
CreateTestProfile();
SetTestUrlResponse(kForm);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetTestUrl()));
std::vector<FieldValue> empty = kEmptyAddress;
empty.insert(empty.begin() + 6, {"state_freeform", ""});
std::vector<FieldValue> filled = kDefaultAddress;
filled.insert(filled.begin() + 6, {"state_freeform", ""});
ASSERT_TRUE(AutofillFlow(
GetElementById("firstname"), this,
{.show_method = ShowMethod::ByChar('M'),
.after_select = ExpectValues(MergeValue(empty, {"firstname", "M"}),
ElementExpr("document.forms[0]"))}));
EXPECT_THAT(GetFormValues(ElementExpr("document.forms[0]")),
ValuesAre(filled));
}
// Test that we properly autofill forms with non-autofillable fields.
IN_PROC_BROWSER_TEST_F(AutofillInteractiveTest,
AutofillFormWithAutocompleteOffField) {
static const char kForm[] =
R"( <form action="https://www.example.com/" method="POST">
<label for="firstname">First name:</label>
<input type="text" id="firstname"><br>
<label for="middlename">Middle name:</label>
<input type="text" id="middlename" autocomplete="off" /><br>
<label for="lastname">Last name:</label>
<input type="text" id="lastname"><br>
<label for="address1">Address line 1:</label>
<input type="text" id="address1"><br>
<label for="address2">Address line 2:</label>
<input type="text" id="address2"><br>
<label for="city">City:</label>
<input type="text" id="city"><br>
<label for="state">State:</label>
<select id="state">
<option value="" selected="yes">--</option>
<option value="CA">California</option>
<option value="TX">Texas</option>
</select><br>
<label for="zip">ZIP code:</label>
<input type="text" id="zip"><br>
<label for="country">Country:</label>
<select id="country">
<option value="" selected="yes">--</option>
<option value="CA">Canada</option>
<option value="US">United States</option>
</select><br>
<label for="phone">Phone number:</label>
<input type="text" id="phone"><br>
</form> )";
CreateTestProfile();
SetTestUrlResponse(kForm);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetTestUrl()));
std::vector<FieldValue> empty = kEmptyAddress;
empty.insert(empty.begin() + 1, {"middlename", ""});
std::vector<FieldValue> filled = kDefaultAddress;
filled.insert(filled.begin() + 1, {"middlename", "C."});
ASSERT_TRUE(AutofillFlow(
GetElementById("firstname"), this,
{.show_method = ShowMethod::ByChar('M'),
.after_select = ExpectValues(MergeValue(empty, {"firstname", "M"}),
ElementExpr("document.forms[0]"))}));
EXPECT_THAT(GetFormValues(ElementExpr("document.forms[0]")),
ValuesAre(filled));
}
// Test that we can Autofill dynamically generated forms.
IN_PROC_BROWSER_TEST_F(AutofillInteractiveTest, DynamicFormFill) {
static const char kDynamicForm[] =
R"( <p>Some text to paint</p>
<form id="form" action="https://www.example.com/"
method="POST"></form>
<script>
function AddElement(name, label) {
var form = document.getElementById('form');
var label_text = document.createTextNode(label);
var label_element = document.createElement('label');
label_element.setAttribute('for', name);
label_element.appendChild(label_text);
form.appendChild(label_element);
if (name === 'state' || name === 'country') {
var select_element = document.createElement('select');
select_element.setAttribute('id', name);
select_element.setAttribute('name', name);
/* Add an empty selected option. */
var default_option = new Option('--', '', true);
select_element.appendChild(default_option);
/* Add the other options. */
if (name == 'state') {
var option1 = new Option('California', 'CA');
select_element.appendChild(option1);
var option2 = new Option('Texas', 'TX');
select_element.appendChild(option2);
} else {
var option1 = new Option('Canada', 'CA');
select_element.appendChild(option1);
var option2 = new Option('United States', 'US');
select_element.appendChild(option2);
}
form.appendChild(select_element);
} else {
var input_element = document.createElement('input');
input_element.setAttribute('id', name);
input_element.setAttribute('name', name);
form.appendChild(input_element);
}
form.appendChild(document.createElement('br'));
};
function BuildForm() {
var elements = [
['firstname', 'First name:'],
['lastname', 'Last name:'],
['address1', 'Address line 1:'],
['address2', 'Address line 2:'],
['city', 'City:'],
['state', 'State:'],
['zip', 'ZIP code:'],
['country', 'Country:'],
['phone', 'Phone number:'],
];
for (var i = 0; i < elements.length; i++) {
var name = elements[i][0];
var label = elements[i][1];
AddElement(name, label);
}
};
</script> )";
CreateTestProfile();
SetTestUrlResponse(kDynamicForm);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetTestUrl()));
// Dynamically construct the form.
ASSERT_TRUE(content::ExecuteScript(GetWebContents(), "BuildForm();"));
ASSERT_TRUE(AutofillFlow(GetElementById("firstname"), this,
{.show_method = ShowMethod::ByChar('M'),
.after_select = ExpectValues(
MergeValue(kEmptyAddress, {"firstname", "M"}),
GetElementById("form"))}));
EXPECT_THAT(GetFormValues(GetElementById("form")),
ValuesAre(kDefaultAddress));
}
// Test that form filling works after reloading the current page.
IN_PROC_BROWSER_TEST_F(AutofillInteractiveTest, AutofillAfterReload) {
CreateTestProfile();
SetTestUrlResponse(kTestShippingFormString);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetTestUrl()));
// Reload the page.
content::WebContents* web_contents = GetWebContents();
web_contents->GetController().Reload(content::ReloadType::NORMAL, false);
EXPECT_TRUE(content::WaitForLoadStop(web_contents));
ASSERT_TRUE(AutofillFlow(GetElementById("firstname"), this,
{.show_method = ShowMethod::ByChar('M'),
.after_select = ExpectValues(MergeValue(
kEmptyAddress, {"firstname", "M"}))}));
EXPECT_THAT(GetFormValues(), ValuesAre(kDefaultAddress));
}
// Test that filling a form sends all the expected events to the different
// fields being filled.
IN_PROC_BROWSER_TEST_F(AutofillInteractiveTest, AutofillEvents) {
// TODO(crbug.com/609861): Remove the autocomplete attribute from the textarea
// field when the bug is fixed.
static const char kTestEventFormString[] =
R"( <script type="text/javascript">
var inputfocus = false;
var inputkeydown = false;
var inputinput = false;
var inputchange = false;
var inputkeyup = false;
var inputblur = false;
var textfocus = false;
var textkeydown = false;
var textinput= false;
var textchange = false;
var textkeyup = false;
var textblur = false;
var selectfocus = false;
var selectinput = false;
var selectchange = false;
var selectblur = false;
</script>
A form for testing events.
<form action="https://www.example.com/" method="POST" id="shipping">
<label for="firstname">First name:</label>
<input type="text" id="firstname"><br>
<label for="lastname">Last name:</label>
<input type="text" id="lastname"
onfocus="inputfocus = true" onkeydown="inputkeydown = true"
oninput="inputinput = true" onchange="inputchange = true"
onkeyup="inputkeyup = true" onblur="inputblur = true" ><br>
<label for="address1">Address line 1:</label>
<input type="text" id="address1"><br>
<label for="address2">Address line 2:</label>
<input type="text" id="address2"><br>
<label for="city">City:</label>
<textarea rows="4" cols="50" id="city" name="city"
autocomplete="address-level2" onfocus="textfocus = true"
onkeydown="textkeydown = true" oninput="textinput = true"
onchange="textchange = true" onkeyup="textkeyup = true"
onblur="textblur = true"></textarea><br>
<label for="state">State:</label>
<select id="state"
onfocus="selectfocus = true" oninput="selectinput = true"
onchange="selectchange = true" onblur="selectblur = true" >
<option value="" selected="yes">--</option>
<option value="CA">California</option>
<option value="NY">New York</option>
<option value="TX">Texas</option>
</select><br>
<label for="zip">ZIP code:</label>
<input type="text" id="zip"><br>
<label for="country">Country:</label>
<select id="country">
<option value="" selected="yes">--</option>
<option value="CA">Canada</option>
<option value="US">United States</option>
</select><br>
<label for="phone">Phone number:</label>
<input type="text" id="phone"><br>
</form> )";
CreateTestProfile();
SetTestUrlResponse(kTestEventFormString);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetTestUrl()));
ASSERT_TRUE(AutofillFlow(GetElementById("firstname"), this,
{.show_method = ShowMethod::ByChar('M'),
.after_select = ExpectValues(MergeValue(
kEmptyAddress, {"firstname", "M"}))}));
EXPECT_THAT(GetFormValues(), ValuesAre(kDefaultAddress));
// Checks that all the events were fired for the input field.
bool input_focus_triggered;
EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
GetWebContents(), "domAutomationController.send(inputfocus);",
&input_focus_triggered));
EXPECT_TRUE(input_focus_triggered);
bool input_keydown_triggered;
EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
GetWebContents(), "domAutomationController.send(inputkeydown);",
&input_keydown_triggered));
EXPECT_TRUE(input_keydown_triggered);
bool input_input_triggered;
EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
GetWebContents(), "domAutomationController.send(inputinput);",
&input_input_triggered));
EXPECT_TRUE(input_input_triggered);
bool input_change_triggered;
EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
GetWebContents(), "domAutomationController.send(inputchange);",
&input_change_triggered));
EXPECT_TRUE(input_change_triggered);
bool input_keyup_triggered;
EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
GetWebContents(), "domAutomationController.send(inputkeyup);",
&input_keyup_triggered));
EXPECT_TRUE(input_keyup_triggered);
bool input_blur_triggered;
EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
GetWebContents(), "domAutomationController.send(inputblur);",
&input_blur_triggered));
EXPECT_TRUE(input_blur_triggered);
// Checks that all the events were fired for the textarea field.
bool text_focus_triggered;
EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
GetWebContents(), "domAutomationController.send(textfocus);",
&text_focus_triggered));
EXPECT_TRUE(text_focus_triggered);
bool text_keydown_triggered;
EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
GetWebContents(), "domAutomationController.send(textkeydown);",
&text_keydown_triggered));
EXPECT_TRUE(text_keydown_triggered);
bool text_input_triggered;
EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
GetWebContents(), "domAutomationController.send(textinput);",
&text_input_triggered));
EXPECT_TRUE(text_input_triggered);
bool text_change_triggered;
EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
GetWebContents(), "domAutomationController.send(textchange);",
&text_change_triggered));
EXPECT_TRUE(text_change_triggered);
bool text_keyup_triggered;
EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
GetWebContents(), "domAutomationController.send(textkeyup);",
&text_keyup_triggered));
EXPECT_TRUE(text_keyup_triggered);
bool text_blur_triggered;
EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
GetWebContents(), "domAutomationController.send(textblur);",
&text_blur_triggered));
EXPECT_TRUE(text_blur_triggered);
// Checks that all the events were fired for the select field.
bool select_focus_triggered;
EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
GetWebContents(), "domAutomationController.send(selectfocus);",
&select_focus_triggered));
EXPECT_TRUE(select_focus_triggered);
bool select_input_triggered;
EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
GetWebContents(), "domAutomationController.send(selectinput);",
&select_input_triggered));
EXPECT_TRUE(select_input_triggered);
bool select_change_triggered;
EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
GetWebContents(), "domAutomationController.send(selectchange);",
&select_change_triggered));
EXPECT_TRUE(select_change_triggered);
bool select_blur_triggered;
EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
GetWebContents(), "domAutomationController.send(selectblur);",
&select_blur_triggered));
EXPECT_TRUE(select_blur_triggered);
}
IN_PROC_BROWSER_TEST_F(AutofillInteractiveTest, AutofillAfterTranslate) {
ASSERT_TRUE(TranslateService::IsTranslateBubbleEnabled());
translate::TranslateManager::SetIgnoreMissingKeyForTesting(true);
CreateTestProfile();
static const char kForm[] =
R"( <form action="https://www.example.com/" method="POST">
<label for="fn">Nom</label>
<input type="text" id="fn"><br>
<label for="ln">Nom de famille</label>
<input type="text" id="ln"><br>
<label for="a1">Address line 1:</label>
<input type="text" id="a1"><br>
<label for="a2">Address line 2:</label>
<input type="text" id="a2"><br>
<label for="ci">City:</label>
<input type="text" id="ci"><br>
<label for="st">State:</label>
<select id="st">
<option value="" selected="yes">--</option>
<option value="CA">California</option>
<option value="TX">Texas</option>
</select><br>
<label for="z">ZIP code:</label>
<input type="text" id="z"><br>
<label for="co">Country:</label>
<select id="co">
<option value="" selected="yes">--</option>
<option value="CA">Canada</option>
<option value="US">United States</option>
</select><br>
<label for="ph">Phone number:</label>
<input type="text" id="ph"><br>
</form>
Nous serons importants et intéressants, mais les épreuves et les
peines peuvent lui en procurer de grandes en raison de situations
occasionnelles.
Puis quelques avantages
)";
// The above additional French words ensure the translate bar will appear.
//
// TODO(crbug.com/1258185): The current translate testing overrides the
// result to be Adopted Language: 'fr' (the language the Chrome's
// translate feature believes the page language to be in). The behavior
// required here is to only force a translation which should not rely on
// language detection. The override simply just seeds the translate code
// so that a translate event occurs in a more testable way.
NavigateToContentAndWaitForLanguageDetection(kForm);
ASSERT_EQ("fr", GetLanguageState().current_language());
ASSERT_NO_FATAL_FAILURE(Translate(true));
ASSERT_EQ("fr", GetLanguageState().source_language());
ASSERT_EQ("en", GetLanguageState().current_language());
ASSERT_TRUE(AutofillFlow(GetElementById("firstname"), this,
{.show_method = ShowMethod::ByChar('M'),
.after_select = ExpectValues(MergeValue(
kEmptyAddress, {"firstname", "M"}))}));
EXPECT_THAT(GetFormValues(), ValuesAre(kDefaultAddress));
}
// Test phone fields parse correctly from a given profile.
// The high level key presses execute the following: Select the first text
// field, invoke the autofill popup list, select the first profile within the
// list, and commit to the profile to populate the form.
IN_PROC_BROWSER_TEST_F(AutofillInteractiveTest, ComparePhoneNumbers) {
AutofillProfile profile;
profile.SetRawInfo(NAME_FIRST, u"Bob");
profile.SetRawInfo(NAME_LAST, u"Smith");
profile.SetRawInfo(ADDRESS_HOME_LINE1, u"1234 H St.");
profile.SetRawInfo(ADDRESS_HOME_CITY, u"San Jose");
profile.SetRawInfo(ADDRESS_HOME_STATE, u"CA");
profile.SetRawInfo(ADDRESS_HOME_ZIP, u"95110");
profile.SetRawInfo(ADDRESS_HOME_COUNTRY, u"US");
profile.SetRawInfo(PHONE_HOME_WHOLE_NUMBER, u"1-408-555-4567");
SetTestProfile(browser()->profile(), profile);
GURL url = embedded_test_server()->GetURL("/autofill/form_phones.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
ASSERT_TRUE(AutofillFlow(GetElementById("NAME_FIRST1"), this));
EXPECT_EQ("Bob", GetFieldValueById("NAME_FIRST1"));
EXPECT_EQ("Smith", GetFieldValueById("NAME_LAST1"));
EXPECT_EQ("1234 H St.", GetFieldValueById("ADDRESS_HOME_LINE1"));
EXPECT_EQ("San Jose", GetFieldValueById("ADDRESS_HOME_CITY"));
EXPECT_EQ("CA", GetFieldValueById("ADDRESS_HOME_STATE"));
EXPECT_EQ("95110", GetFieldValueById("ADDRESS_HOME_ZIP"));
EXPECT_EQ("14085554567", GetFieldValueById("PHONE_HOME_WHOLE_NUMBER"));
ASSERT_TRUE(AutofillFlow(GetElementById("NAME_FIRST2"), this));
EXPECT_EQ("Bob", GetFieldValueById("NAME_FIRST2"));
EXPECT_EQ("Smith", GetFieldValueById("NAME_LAST2"));
EXPECT_EQ("408", GetFieldValueById("PHONE_HOME_CITY_CODE-1"));
EXPECT_EQ("5554567", GetFieldValueById("PHONE_HOME_NUMBER"));
ASSERT_TRUE(AutofillFlow(GetElementById("NAME_FIRST3"), this));
EXPECT_EQ("Bob", GetFieldValueById("NAME_FIRST3"));
EXPECT_EQ("Smith", GetFieldValueById("NAME_LAST3"));
EXPECT_EQ("408", GetFieldValueById("PHONE_HOME_CITY_CODE-2"));
EXPECT_EQ("555", GetFieldValueById("PHONE_HOME_NUMBER_3-1"));
EXPECT_EQ("4567", GetFieldValueById("PHONE_HOME_NUMBER_4-1"));
EXPECT_EQ("", GetFieldValueById("PHONE_HOME_EXT-1"));
ASSERT_TRUE(AutofillFlow(GetElementById("NAME_FIRST4"), this));
EXPECT_EQ("Bob", GetFieldValueById("NAME_FIRST4"));
EXPECT_EQ("Smith", GetFieldValueById("NAME_LAST4"));
EXPECT_EQ("1", GetFieldValueById("PHONE_HOME_COUNTRY_CODE-1"));
EXPECT_EQ("408", GetFieldValueById("PHONE_HOME_CITY_CODE-3"));
EXPECT_EQ("555", GetFieldValueById("PHONE_HOME_NUMBER_3-2"));
EXPECT_EQ("4567", GetFieldValueById("PHONE_HOME_NUMBER_4-2"));
EXPECT_EQ("", GetFieldValueById("PHONE_HOME_EXT-2"));
}
// Test that Autofill does not fill in Company Name if disabled
IN_PROC_BROWSER_TEST_F(AutofillInteractiveTest, NoAutofillForCompanyName) {
std::string addr_line1("1234 H St.");
std::string company_name("Company X");
AutofillProfile profile;
profile.SetRawInfo(NAME_FIRST, u"Bob");
profile.SetRawInfo(NAME_LAST, u"Smith");
profile.SetRawInfo(EMAIL_ADDRESS, u"bsmith@gmail.com");
profile.SetRawInfo(ADDRESS_HOME_LINE1, ASCIIToUTF16(addr_line1));
profile.SetRawInfo(ADDRESS_HOME_CITY, u"San Jose");
profile.SetRawInfo(ADDRESS_HOME_STATE, u"CA");
profile.SetRawInfo(ADDRESS_HOME_ZIP, u"95110");
profile.SetRawInfo(COMPANY_NAME, ASCIIToUTF16(company_name));
profile.SetRawInfo(PHONE_HOME_WHOLE_NUMBER, u"408-871-4567");
SetTestProfile(browser()->profile(), profile);
GURL url =
embedded_test_server()->GetURL("/autofill/read_only_field_test.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
ASSERT_TRUE(AutofillFlow(GetElementById("firstname"), this));
EXPECT_EQ(addr_line1, GetFieldValueById("address"));
EXPECT_EQ(company_name, GetFieldValueById("company"));
}
// TODO(https://crbug.com/1279102): Check back if flakiness is fixed now.
IN_PROC_BROWSER_TEST_F(AutofillInteractiveTest,
NoAutofillSugggestionForCompanyName) {
static const char kTestShippingFormWithCompanyString[] = R"(
An example of a shipping address form.
<form action="https://www.example.com/" method="POST">
<label for="firstname">First name:</label>
<input type="text" id="firstname"><br>
<label for="lastname">Last name:</label>
<input type="text" id="lastname"><br>
<label for="address1">Address line 1:</label>
<input type="text" id="address1"><br>
<label for="address2">Address line 2:</label>
<input type="text" id="address2"><br>
<label for="city">City:</label>
<input type="text" id="city"><br>
<label for="state">State:</label>
<select id="state">
<option value="" selected="yes">--</option>
<option value="CA">California</option>
<option value="TX">Texas</option>
</select><br>
<label for="zip">ZIP code:</label>
<input type="text" id="zip"><br>
<label for="country">Country:</label>
<select id="country">
<option value="" selected="yes">--</option>
<option value="CA">Canada</option>
<option value="US">United States</option>
</select><br>
<label for="phone">Phone number:</label>
<input type="text" id="phone"><br>
<label for="company">First company:</label>
<input type="text" id="company"><br>
</form>
)";
CreateTestProfile();
SetTestUrlResponse(kTestShippingFormWithCompanyString);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetTestUrl()));
ASSERT_TRUE(AutofillFlow(GetElementById("firstname"), this,
{.show_method = ShowMethod::ByClick(),
.execution_target = GetWebContents()}));
EXPECT_EQ("Initech", GetFieldValueById("company"));
}
// Test that Autofill does not fill in read-only fields.
IN_PROC_BROWSER_TEST_F(AutofillInteractiveTest, NoAutofillForReadOnlyFields) {
std::string addr_line1("1234 H St.");
AutofillProfile profile;
profile.SetRawInfo(NAME_FIRST, u"Bob");
profile.SetRawInfo(NAME_LAST, u"Smith");
profile.SetRawInfo(EMAIL_ADDRESS, u"bsmith@gmail.com");
profile.SetRawInfo(ADDRESS_HOME_LINE1, ASCIIToUTF16(addr_line1));
profile.SetRawInfo(ADDRESS_HOME_CITY, u"San Jose");
profile.SetRawInfo(ADDRESS_HOME_STATE, u"CA");
profile.SetRawInfo(ADDRESS_HOME_ZIP, u"95110");
profile.SetRawInfo(COMPANY_NAME, u"Company X");
profile.SetRawInfo(PHONE_HOME_WHOLE_NUMBER, u"408-871-4567");
SetTestProfile(browser()->profile(), profile);
GURL url =
embedded_test_server()->GetURL("/autofill/read_only_field_test.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
ASSERT_TRUE(AutofillFlow(GetElementById("firstname"), this));
EXPECT_EQ("", GetFieldValueById("email"));
EXPECT_EQ(addr_line1, GetFieldValueById("address"));
}
// Test form is fillable from a profile after form was reset.
// Steps:
// 1. Fill form using a saved profile.
// 2. Reset the form.
// 3. Fill form using a saved profile.
IN_PROC_BROWSER_TEST_F(AutofillInteractiveTest, FormFillableOnReset) {
CreateTestProfile();
GURL url =
embedded_test_server()->GetURL("/autofill/autofill_test_form.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
ASSERT_TRUE(AutofillFlow(GetElementById("NAME_FIRST"), this));
ASSERT_TRUE(content::ExecuteScript(
GetWebContents(), "document.getElementById('testform').reset()"));
ASSERT_TRUE(AutofillFlow(GetElementById("NAME_FIRST"), this));
EXPECT_EQ("Milton", GetFieldValueById("NAME_FIRST"));
EXPECT_EQ("Waddams", GetFieldValueById("NAME_LAST"));
EXPECT_EQ("red.swingline@initech.com", GetFieldValueById("EMAIL_ADDRESS"));
EXPECT_EQ("4120 Freidrich Lane", GetFieldValueById("ADDRESS_HOME_LINE1"));
EXPECT_EQ("Austin", GetFieldValueById("ADDRESS_HOME_CITY"));
EXPECT_EQ("Texas", GetFieldValueById("ADDRESS_HOME_STATE"));
EXPECT_EQ("78744", GetFieldValueById("ADDRESS_HOME_ZIP"));
EXPECT_EQ("United States", GetFieldValueById("ADDRESS_HOME_COUNTRY"));
EXPECT_EQ("15125551234", GetFieldValueById("PHONE_HOME_WHOLE_NUMBER"));
}
// Test Autofill distinguishes a middle initial in a name.
IN_PROC_BROWSER_TEST_F(AutofillInteractiveTest,
DistinguishMiddleInitialWithinName) {
CreateTestProfile();
GURL url =
embedded_test_server()->GetURL("/autofill/autofill_middleinit_form.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
ASSERT_TRUE(AutofillFlow(GetElementById("NAME_FIRST"), this));
EXPECT_EQ("C.", GetFieldValueById("NAME_MIDDLE"));
}
// Test forms with multiple email addresses are filled properly.
// Entire form should be filled with one user gesture.
IN_PROC_BROWSER_TEST_F(AutofillInteractiveTest,
MultipleEmailFilledByOneUserGesture) {
std::string email("bsmith@gmail.com");
AutofillProfile profile;
profile.SetRawInfo(NAME_FIRST, u"Bob");
profile.SetRawInfo(NAME_LAST, u"Smith");
profile.SetRawInfo(EMAIL_ADDRESS, ASCIIToUTF16(email));
profile.SetRawInfo(PHONE_HOME_WHOLE_NUMBER, u"4088714567");
SetTestProfile(browser()->profile(), profile);
GURL url = embedded_test_server()->GetURL(
"/autofill/autofill_confirmemail_form.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
ASSERT_TRUE(AutofillFlow(GetElementById("NAME_FIRST"), this));
EXPECT_EQ(email, GetFieldValueById("EMAIL_CONFIRM"));
// TODO(isherman): verify entire form.
}
// Test latency time on form submit with lots of stored Autofill profiles.
// This test verifies when a profile is selected from the Autofill dictionary
// that consists of thousands of profiles, the form does not hang after being
// submitted.
// Flakily times out creating 1500 profiles: http://crbug.com/281527
IN_PROC_BROWSER_TEST_F(AutofillInteractiveTest,
DISABLED_FormFillLatencyAfterSubmit) {
std::vector<std::string> cities;
cities.push_back("San Jose");
cities.push_back("San Francisco");
cities.push_back("Sacramento");
cities.push_back("Los Angeles");
std::vector<std::string> streets;
streets.push_back("St");
streets.push_back("Ave");
streets.push_back("Ln");
streets.push_back("Ct");
constexpr int kNumProfiles = 1500;
std::vector<AutofillProfile> profiles;
for (int i = 0; i < kNumProfiles; i++) {
AutofillProfile profile;
std::u16string name(base::NumberToString16(i));
std::u16string email(name + u"@example.com");
std::u16string street =
ASCIIToUTF16(base::NumberToString(base::RandInt(0, 10000)) + " " +
streets[base::RandInt(0, streets.size() - 1)]);
std::u16string city =
ASCIIToUTF16(cities[base::RandInt(0, cities.size() - 1)]);
std::u16string zip(base::NumberToString16(base::RandInt(0, 10000)));
profile.SetRawInfo(NAME_FIRST, name);
profile.SetRawInfo(EMAIL_ADDRESS, email);
profile.SetRawInfo(ADDRESS_HOME_LINE1, street);
profile.SetRawInfo(ADDRESS_HOME_CITY, city);
profile.SetRawInfo(ADDRESS_HOME_STATE, u"CA");
profile.SetRawInfo(ADDRESS_HOME_ZIP, zip);
profile.SetRawInfo(ADDRESS_HOME_COUNTRY, u"US");
profiles.push_back(profile);
}
SetTestProfiles(browser()->profile(), &profiles);
GURL url = embedded_test_server()->GetURL(
"/autofill/latency_after_submit_test.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
ASSERT_TRUE(AutofillFlow(GetElementById("NAME_FIRST"), this));
content::LoadStopObserver load_stop_observer(GetWebContents());
ASSERT_TRUE(content::ExecuteScript(
GetWebContents(), "document.getElementById('testform').submit();"));
// This will ensure the test didn't hang.
load_stop_observer.Wait();
}
// Test that Chrome doesn't crash when autocomplete is disabled while the user
// is interacting with the form. This is a regression test for
// http://crbug.com/160476
IN_PROC_BROWSER_TEST_F(AutofillInteractiveTest,
DisableAutocompleteWhileFilling) {
CreateTestProfile();
SetTestUrlResponse(kTestShippingFormString);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetTestUrl()));
// When suggestions are shown, disable autocomplete for the active field.
auto SetAutocompleteOff = [this]() {
ASSERT_TRUE(content::ExecuteScript(
GetWebContents(),
"document.querySelector('input').autocomplete = 'off';"));
};
ASSERT_TRUE(AutofillFlow(
GetElementById("firstname"), this,
{.show_method = ShowMethod::ByChar('M'),
.after_select = base::BindLambdaForTesting(SetAutocompleteOff)}));
}
// Test that a page with 2 forms with no name and id containing fields with no
// name or if get filled properly.
IN_PROC_BROWSER_TEST_F(AutofillInteractiveTest,
FillFormAndFieldWithNoNameOrId) {
CreateTestProfile();
GURL url = embedded_test_server()->GetURL(
"/autofill/forms_without_identifiers.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
auto name = ElementExpr("document.forms[1].elements[0]");
auto email = ElementExpr("document.forms[1].elements[1]");
ASSERT_TRUE(
AutofillFlow(name, this, {.show_method = ShowMethod::ByChar('M')}));
EXPECT_EQ("Milton C. Waddams", GetFieldValue(name));
EXPECT_EQ("red.swingline@initech.com", GetFieldValue(email));
}
// The following four tests verify that we can autofill forms with multiple
// nameless forms, and repetitive field names and make sure that the dynamic
// refill would not trigger a wrong refill, regardless of the form.
IN_PROC_BROWSER_TEST_F(AutofillInteractiveTest,
Dynamic_MultipleNoNameForms_BadNames_FourthForm) {
CreateTestProfile();
GURL url = embedded_test_server()->GetURL(
"a.com", "/autofill/multiple_noname_forms_badnames.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
ASSERT_TRUE(AutofillFlow(GetElementById("firstname_4"), this));
DoNothingAndWait(base::Seconds(2)); // Wait to for possible refills.
EXPECT_EQ("", GetFieldValueById("firstname_1"));
EXPECT_EQ("", GetFieldValueById("lastname_1"));
EXPECT_EQ("", GetFieldValueById("email_1"));
EXPECT_EQ("", GetFieldValueById("firstname_2"));
EXPECT_EQ("", GetFieldValueById("lastname_2"));
EXPECT_EQ("", GetFieldValueById("email_2"));
EXPECT_EQ("", GetFieldValueById("firstname_3"));
EXPECT_EQ("", GetFieldValueById("lastname_3"));
EXPECT_EQ("", GetFieldValueById("email_3"));
EXPECT_EQ("Milton", GetFieldValueById("firstname_4"));
EXPECT_EQ("Waddams", GetFieldValueById("lastname_4"));
EXPECT_EQ("red.swingline@initech.com", GetFieldValueById("email_4"));
}
IN_PROC_BROWSER_TEST_F(AutofillInteractiveTest,
Dynamic_MultipleNoNameForms_BadNames_ThirdForm) {
CreateTestProfile();
GURL url = embedded_test_server()->GetURL(
"a.com", "/autofill/multiple_noname_forms_badnames.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
ASSERT_TRUE(AutofillFlow(GetElementById("firstname_3"), this));
DoNothingAndWait(base::Seconds(2)); // Wait to for possible refills.
EXPECT_EQ("", GetFieldValueById("firstname_1"));
EXPECT_EQ("", GetFieldValueById("lastname_1"));
EXPECT_EQ("", GetFieldValueById("email_1"));
EXPECT_EQ("", GetFieldValueById("firstname_2"));
EXPECT_EQ("", GetFieldValueById("lastname_2"));
EXPECT_EQ("", GetFieldValueById("email_2"));
EXPECT_EQ("Milton", GetFieldValueById("firstname_3"));
EXPECT_EQ("Waddams", GetFieldValueById("lastname_3"));
EXPECT_EQ("red.swingline@initech.com", GetFieldValueById("email_3"));
EXPECT_EQ("", GetFieldValueById("firstname_4"));
EXPECT_EQ("", GetFieldValueById("lastname_4"));
EXPECT_EQ("", GetFieldValueById("email_4"));
}
IN_PROC_BROWSER_TEST_F(AutofillInteractiveTest,
Dynamic_MultipleNoNameForms_BadNames_SecondForm) {
CreateTestProfile();
GURL url = embedded_test_server()->GetURL(
"a.com", "/autofill/multiple_noname_forms_badnames.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
ASSERT_TRUE(AutofillFlow(GetElementById("firstname_2"), this));
DoNothingAndWait(base::Seconds(2)); // Wait to for possible refills.
EXPECT_EQ("", GetFieldValueById("firstname_1"));
EXPECT_EQ("", GetFieldValueById("lastname_1"));
EXPECT_EQ("", GetFieldValueById("email_1"));
EXPECT_EQ("Milton", GetFieldValueById("firstname_2"));
EXPECT_EQ("Waddams", GetFieldValueById("lastname_2"));
EXPECT_EQ("red.swingline@initech.com", GetFieldValueById("email_2"));
EXPECT_EQ("", GetFieldValueById("firstname_3"));
EXPECT_EQ("", GetFieldValueById("lastname_3"));
EXPECT_EQ("", GetFieldValueById("email_3"));
}
IN_PROC_BROWSER_TEST_F(AutofillInteractiveTest,
Dynamic_MultipleNoNameForms_BadNames_FirstForm) {
CreateTestProfile();
GURL url = embedded_test_server()->GetURL(
"a.com", "/autofill/multiple_noname_forms_badnames.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
ASSERT_TRUE(AutofillFlow(GetElementById("firstname_1"), this));
DoNothingAndWait(base::Seconds(2)); // Wait to for possible refills.
EXPECT_EQ("Milton", GetFieldValueById("firstname_1"));
EXPECT_EQ("Waddams", GetFieldValueById("lastname_1"));
EXPECT_EQ("red.swingline@initech.com", GetFieldValueById("email_1"));
EXPECT_EQ("", GetFieldValueById("firstname_2"));
EXPECT_EQ("", GetFieldValueById("lastname_2"));
EXPECT_EQ("", GetFieldValueById("email_2"));
EXPECT_EQ("", GetFieldValueById("firstname_3"));
EXPECT_EQ("", GetFieldValueById("lastname_3"));
EXPECT_EQ("", GetFieldValueById("email_3"));
}
// Test that we can Autofill forms where some fields name change during the
// fill.
IN_PROC_BROWSER_TEST_F(AutofillInteractiveTest, FieldsChangeName) {
CreateTestProfile();
GURL url = embedded_test_server()->GetURL(
"/autofill/field_changing_name_during_fill.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
ASSERT_TRUE(AutofillFlow(GetElementById("firstname"), this));
EXPECT_EQ("Milton", GetFieldValueById("firstname"));
EXPECT_EQ("4120 Freidrich Lane", GetFieldValueById("address"));
EXPECT_EQ("TX", GetFieldValueById("state"));
EXPECT_EQ("Austin", GetFieldValueById("city"));
EXPECT_EQ("Initech", GetFieldValueById("company"));
EXPECT_EQ("red.swingline@initech.com", GetFieldValueById("email"));
EXPECT_EQ("15125551234", GetFieldValueById("phone"));
}
// Test that credit card autofill works.
IN_PROC_BROWSER_TEST_F(AutofillInteractiveTestBase, FillLocalCreditCard) {
CreateTestCreditCart();
GURL url = https_server()->GetURL("a.com",
"/autofill/autofill_creditcard_form.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
ASSERT_TRUE(AutofillFlow(GetElementById("CREDIT_CARD_NAME_FULL"), this));
EXPECT_EQ("Milton Waddams", GetFieldValueById("CREDIT_CARD_NAME_FULL"));
EXPECT_EQ("4111111111111111", GetFieldValueById("CREDIT_CARD_NUMBER"));
EXPECT_EQ("09", GetFieldValueById("CREDIT_CARD_EXP_MONTH"));
EXPECT_EQ("2999", GetFieldValueById("CREDIT_CARD_EXP_4_DIGIT_YEAR"));
}
// Test that we do not fill formless non-checkout forms when we enable the
// formless form restrictions.
IN_PROC_BROWSER_TEST_F(AutofillInteractiveTestBase, NoAutocomplete) {
CreateTestProfile();
GURL url =
embedded_test_server()->GetURL("/autofill/formless_no_autocomplete.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
ASSERT_TRUE(AutofillFlow(GetElementById("firstname"), this));
::metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
// If only some form fields are tagged with autocomplete types, then the
// number of input elements will not match the number of fields when autofill
// triees to preview or fill.
histogram_tester().ExpectUniqueSample("Autofill.NumElementsMatchesNumFields",
true, 2);
EXPECT_EQ("Milton", GetFieldValueById("firstname"));
EXPECT_EQ("4120 Freidrich Lane", GetFieldValueById("address"));
EXPECT_EQ("TX", GetFieldValueById("state"));
EXPECT_EQ("Austin", GetFieldValueById("city"));
EXPECT_EQ("Initech", GetFieldValueById("company"));
EXPECT_EQ("red.swingline@initech.com", GetFieldValueById("email"));
EXPECT_EQ("15125551234", GetFieldValueById("phone"));
}
// Test that we do not fill formless non-checkout forms when we enable the
// formless form restrictions. This test differes from the NoAutocomplete
// version of the the test in that at least one of the fields has an
// autocomplete attribute, so autofill will always be aware of the existence
// of the form.
IN_PROC_BROWSER_TEST_F(AutofillInteractiveTestBase, SomeAutocomplete) {
CreateTestProfile();
GURL url = embedded_test_server()->GetURL(
"/autofill/formless_some_autocomplete.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
ASSERT_TRUE(AutofillFlow(GetElementById("firstname"), this));
::metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
// If only some form fields are tagged with autocomplete types, then the
// number of input elements will not match the number of fields when autofill
// triees to preview or fill.
histogram_tester().ExpectUniqueSample("Autofill.NumElementsMatchesNumFields",
true, 2);
EXPECT_EQ("Milton", GetFieldValueById("firstname"));
EXPECT_EQ("4120 Freidrich Lane", GetFieldValueById("address"));
EXPECT_EQ("TX", GetFieldValueById("state"));
EXPECT_EQ("Austin", GetFieldValueById("city"));
EXPECT_EQ("Initech", GetFieldValueById("company"));
EXPECT_EQ("red.swingline@initech.com", GetFieldValueById("email"));
EXPECT_EQ("15125551234", GetFieldValueById("phone"));
}
// Test that we do not fill formless non-checkout forms when we enable the
// formless form restrictions.
// TODO(https://crbug.com/1297560): Check back if flakiness is fixed now.
IN_PROC_BROWSER_TEST_F(AutofillInteractiveTestBase, AllAutocomplete) {
CreateTestProfile();
GURL url = embedded_test_server()->GetURL(
"/autofill/formless_all_autocomplete.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
ASSERT_TRUE(AutofillFlow(GetElementById("firstname"), this));
::metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
// If all form fields are tagged with autocomplete types, we make them all
// available to be filled.
histogram_tester().ExpectUniqueSample("Autofill.NumElementsMatchesNumFields",
true, 2);
EXPECT_EQ("Milton", GetFieldValueById("firstname"));
EXPECT_EQ("4120 Freidrich Lane", GetFieldValueById("address"));
EXPECT_EQ("TX", GetFieldValueById("state"));
EXPECT_EQ("Austin", GetFieldValueById("city"));
EXPECT_EQ("Initech", GetFieldValueById("company"));
EXPECT_EQ("red.swingline@initech.com", GetFieldValueById("email"));
EXPECT_EQ("15125551234", GetFieldValueById("phone"));
}
// An extension of the test fixture for tests with site isolation.
class AutofillInteractiveIsolationTest : public AutofillInteractiveTestBase {
protected:
AutofillInteractiveIsolationTest() = default;
~AutofillInteractiveIsolationTest() override = default;
bool IsPopupShown() {
return !!ChromeAutofillClient::FromWebContentsForTesting(GetWebContents())
->popup_controller_for_testing();
}
private:
void SetUpCommandLine(base::CommandLine* command_line) override {
AutofillInteractiveTestBase::SetUpCommandLine(command_line);
// Append --site-per-process flag.
content::IsolateAllSitesForTesting(command_line);
}
};
enum class FrameType { kIFrame, kFencedFrame };
class AutofillInteractiveFencedFrameTest
: public AutofillInteractiveIsolationTest,
public ::testing::WithParamInterface<FrameType> {
protected:
AutofillInteractiveFencedFrameTest() {
std::vector<base::test::FeatureRefAndParams> enabled;
std::vector<base::test::FeatureRef> disabled;
if (GetParam() != FrameType::kIFrame) {
enabled.push_back({blink::features::kBrowsingTopics, {}});
enabled.push_back({blink::features::kBrowsingTopicsXHR, {}});
enabled.push_back({blink::features::kFencedFramesAPIChanges, {}});
enabled.push_back({features::kAutofillEnableWithinFencedFrame, {}});
scoped_feature_list_.InitWithFeaturesAndParameters(enabled, disabled);
fenced_frame_test_helper_ =
std::make_unique<content::test::FencedFrameTestHelper>();
} else {
disabled.push_back(features::kAutofillEnableWithinFencedFrame);
scoped_feature_list_.InitWithFeaturesAndParameters(enabled, disabled);
}
}
~AutofillInteractiveFencedFrameTest() override = default;
content::RenderFrameHost* primary_main_frame_host() {
return GetWebContents()->GetPrimaryMainFrame();
}
content::RenderFrameHost* LoadSubFrame(std::string relative_url) {
GURL frame_url = https_server()->GetURL(
"b.com", (GetParam() == FrameType::kIFrame ? "" : "/fenced_frames") +
relative_url);
switch (GetParam()) {
case FrameType::kIFrame: {
EXPECT_TRUE(content::NavigateIframeToURL(GetWebContents(), "crossFrame",
frame_url));
// TODO(crbug.com/1323334) Use AutofillManager::OnFormParsed instead of
// DoNothingAndWait.
// Wait to make sure the cross-frame form is parsed.
DoNothingAndWait(base::Seconds(2));
content::RenderFrameHost* cross_frame =
RenderFrameHostForName(GetWebContents(), "crossFrame");
return cross_frame;
}
case FrameType::kFencedFrame: {
content::RenderFrameHost* cross_frame =
fenced_frame_test_helper_->CreateFencedFrame(
primary_main_frame_host(), frame_url);
// TODO(crbug.com/1323334) Use AutofillManager::OnFormParsed instead of
// DoNothingAndWait.
// Wait to make sure the cross-frame form is parsed.
DoNothingAndWait(base::Seconds(2));
return cross_frame;
}
}
NOTREACHED();
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
std::unique_ptr<content::test::FencedFrameTestHelper>
fenced_frame_test_helper_;
};
// TODO(https://crbug.com/1175735): Check back if flakiness is fixed now.
IN_PROC_BROWSER_TEST_P(AutofillInteractiveFencedFrameTest,
SimpleCrossSiteFill) {
test_delegate()->SetIgnoreBackToBackMessages(
ObservedUiEvents::kPreviewFormData, true);
CreateTestProfile();
// Main frame is on a.com, iframe/fenced frame is on b.com.
GURL url =
https_server()->GetURL("a.com", "/autofill/cross_origin_iframe.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
content::RenderFrameHost* cross_frame_host =
LoadSubFrame("/autofill/autofill_test_form.html");
ASSERT_TRUE(cross_frame_host);
ContentAutofillDriver* cross_driver =
ContentAutofillDriverFactory::FromWebContents(GetWebContents())
->DriverForFrame(cross_frame_host);
ASSERT_TRUE(cross_driver);
// Let |test_delegate()| also observe autofill events in the iframe.
static_cast<BrowserAutofillManager*>(cross_driver->autofill_manager())
->SetTestDelegate(test_delegate());
ASSERT_TRUE(AutofillFlow(GetElementById("NAME_FIRST"), this,
{.execution_target = cross_frame_host}));
EXPECT_EQ("Milton",
GetFieldValue(GetElementById("NAME_FIRST"), cross_frame_host));
}
// This test verifies that credit card (payment card list) popup works when the
// form is inside an OOPIF/Fenced Frame.
IN_PROC_BROWSER_TEST_P(AutofillInteractiveFencedFrameTest,
CrossSitePaymentForms) {
CreateTestCreditCart();
// Main frame is on a.com, iframe/fenced frame is on b.com.
GURL url =
https_server()->GetURL("a.com", "/autofill/cross_origin_iframe.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
content::RenderFrameHost* cross_frame_host =
LoadSubFrame("/autofill/autofill_creditcard_form.html");
ASSERT_TRUE(cross_frame_host);
ContentAutofillDriver* cross_driver =
ContentAutofillDriverFactory::FromWebContents(GetWebContents())
->DriverForFrame(cross_frame_host);
ASSERT_TRUE(cross_driver);
// Let |test_delegate()| also observe autofill events in the iframe.
static_cast<BrowserAutofillManager*>(cross_driver->autofill_manager())
->SetTestDelegate(test_delegate());
auto Wait = [this]() { DoNothingAndWait(base::Seconds(2)); };
ASSERT_TRUE(AutofillFlow(GetElementById("CREDIT_CARD_NUMBER"), this,
{.after_focus = base::BindLambdaForTesting(Wait),
.execution_target = cross_frame_host}));
}
// TODO(https://crbug.com/1175735): Check back if flakiness is fixed now.
IN_PROC_BROWSER_TEST_P(AutofillInteractiveFencedFrameTest,
DeletingFrameUnderSuggestion) {
// TODO(crbug.com/1240482): the test expectations fail if the window gets CSD
// and becomes smaller because of that. Investigate this and remove the line
// below if possible.
ui::ScopedDisableClientSideDecorationsForTest scoped_disabled_csd;
CreateTestProfile();
// Main frame is on a.com, fenced frame is on b.com.
GURL url =
https_server()->GetURL("a.com", "/autofill/cross_origin_iframe.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
content::RenderFrameHost* cross_frame_host =
LoadSubFrame("/autofill/autofill_test_form.html");
ASSERT_TRUE(cross_frame_host);
// We need the fencedframe element to have id set to a known value
if (GetParam() != FrameType::kIFrame) {
ASSERT_TRUE(content::ExecuteScript(
GetWebContents(),
"document.getElementsByTagName('fencedframe')[0].id = 'crossFF';"));
}
ContentAutofillDriver* cross_driver =
ContentAutofillDriverFactory::FromWebContents(GetWebContents())
->DriverForFrame(cross_frame_host);
ASSERT_TRUE(cross_driver);
// Let |test_delegate()| also observe autofill events in the iframe.
static_cast<BrowserAutofillManager*>(cross_driver->autofill_manager())
->SetTestDelegate(test_delegate());
// Focus the form in the iframe/fenced frame and simulate choosing a
// suggestion via keyboard.
ASSERT_TRUE(
AutofillFlow(GetElementById("NAME_FIRST"), this,
{.do_accept = false, .execution_target = cross_frame_host}));
// Do not accept the suggestion yet, to keep the pop-up shown.
EXPECT_TRUE(IsPopupShown());
// Delete the iframe/fenced frame.
std::string script_delete = base::StringPrintf(
"document.body.removeChild(document.getElementById('%s'))",
GetParam() == FrameType::kIFrame ? "crossFrame" : "crossFF");
ASSERT_TRUE(content::ExecuteScript(GetWebContents(), script_delete));
// The popup should have disappeared with the iframe.
EXPECT_FALSE(IsPopupShown());
}
INSTANTIATE_TEST_SUITE_P(AutofillInteractiveTest,
AutofillInteractiveFencedFrameTest,
::testing::Values(FrameType::kFencedFrame,
FrameType::kIFrame));
// Test fixture for refill behavior.
//
// BrowserAutofillManager only executes a refill if it happens within the time
// delta `kLimitBeforeRefill` of the original refill. On slow bots, this timeout
// may cause flakiness. Therefore, this fixture mocks test clocks, which shall
// be advanced when waiting for a refill after AutofillFlow():
// - advance by a delta less than `kLimitBeforeRefill` to simulate that a
// natural delay between fill and refill;
// - advance by a delta greater than `kLimitBeforeRefill` to simulate that an
// event happens too late to actually trigger a refill.
//
// The boolean parameter controls whether or not
// features::kAutofillAcrossIframes is enabled.
class AutofillInteractiveTestDynamicForm
: public AutofillInteractiveTestBase,
public testing::WithParamInterface<bool> {
public:
AutofillInteractiveTestDynamicForm() {
scoped_feature_list_.InitWithFeatureState(features::kAutofillAcrossIframes,
GetParam());
}
AutofillInteractiveTestDynamicForm(
const AutofillInteractiveTestDynamicForm&) = delete;
AutofillInteractiveTestDynamicForm& operator=(
const AutofillInteractiveTestDynamicForm&) = delete;
~AutofillInteractiveTestDynamicForm() override = default;
ValueWaiter ListenForRefill(
const std::string& id,
absl::optional<std::string> unblock_variable = "refill") {
return ListenForValueChange(id, unblock_variable, GetWebContents());
}
// Refills only happen within `kLimitBeforeRefill` second of the initial fill.
// Slow bots may exceed this limit and thus cause flakiness.
static constexpr base::TimeDelta kLessThanLimitBeforeRefill =
kLimitBeforeRefill / 10;
void AdvanceClock(base::TimeDelta delta) {
clock_.Advance(delta);
tick_clock_.Advance(delta);
}
protected:
base::test::ScopedFeatureList scoped_feature_list_;
TestAutofillClock clock_{AutofillClock::Now()};
TestAutofillTickClock tick_clock_{AutofillTickClock::NowTicks()};
};
INSTANTIATE_TEST_SUITE_P(AutofillInteractiveTest,
AutofillInteractiveTestDynamicForm,
testing::Bool());
// Test that we can Autofill dynamically generated forms.
// TODO(https://crbug.com/1297560): Check back if flakiness is fixed now.
IN_PROC_BROWSER_TEST_P(AutofillInteractiveTestDynamicForm,
DynamicChangingFormFill) {
CreateTestProfile();
GURL url =
embedded_test_server()->GetURL("a.com", "/autofill/dynamic_form.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
ValueWaiter refill = ListenForRefill("firstname_form1");
ASSERT_TRUE(AutofillFlow(GetElementById("firstname"), this));
AdvanceClock(kLessThanLimitBeforeRefill);
ASSERT_TRUE(std::move(refill).Wait());
// Make sure the new form was filled correctly.
EXPECT_EQ("Milton", GetFieldValueById("firstname_form1"));
EXPECT_EQ("4120 Freidrich Lane", GetFieldValueById("address_form1"));
EXPECT_EQ("TX", GetFieldValueById("state_form1"));
EXPECT_EQ("Austin", GetFieldValueById("city_form1"));
EXPECT_EQ("Initech", GetFieldValueById("company_form1"));
EXPECT_EQ("red.swingline@initech.com", GetFieldValueById("email_form1"));
EXPECT_EQ("15125551234", GetFieldValueById("phone_form1"));
}
// TODO(https://crbug.com/1297560): Check back if flakiness is fixed now.
IN_PROC_BROWSER_TEST_P(AutofillInteractiveTestDynamicForm,
TwoDynamicChangingFormsFill) {
CreateTestProfile();
GURL url = embedded_test_server()->GetURL("a.com",
"/autofill/two_dynamic_forms.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
ValueWaiter refill = ListenForRefill("firstname_form1");
ASSERT_TRUE(AutofillFlow(GetElementById("firstname_form1"), this));
AdvanceClock(kLessThanLimitBeforeRefill);
ASSERT_TRUE(std::move(refill).Wait());
// Make sure the new form was filled correctly.
EXPECT_EQ("Milton", GetFieldValueById("firstname_form1"));
EXPECT_EQ("4120 Freidrich Lane", GetFieldValueById("address_form1"));
EXPECT_EQ("TX", GetFieldValueById("state_form1"));
EXPECT_EQ("Austin", GetFieldValueById("city_form1"));
EXPECT_EQ("Initech", GetFieldValueById("company_form1"));
EXPECT_EQ("red.swingline@initech.com", GetFieldValueById("email_form1"));
EXPECT_EQ("15125551234", GetFieldValueById("phone_form1"));
refill = ListenForRefill("firstname_form2");
ASSERT_TRUE(AutofillFlow(GetElementById("firstname_form2"), this));
AdvanceClock(kLessThanLimitBeforeRefill);
ASSERT_TRUE(std::move(refill).Wait());
// Make sure the new form was filled correctly.
EXPECT_EQ("Milton", GetFieldValueById("firstname_form2"));
EXPECT_EQ("4120 Freidrich Lane", GetFieldValueById("address_form2"));
EXPECT_EQ("TX", GetFieldValueById("state_form2"));
EXPECT_EQ("Austin", GetFieldValueById("city_form2"));
EXPECT_EQ("Initech", GetFieldValueById("company_form2"));
EXPECT_EQ("red.swingline@initech.com", GetFieldValueById("email_form2"));
EXPECT_EQ("15125551234", GetFieldValueById("phone_form2"));
}
// Test that forms that dynamically change a second time do not get filled.
IN_PROC_BROWSER_TEST_P(AutofillInteractiveTestDynamicForm,
DynamicChangingFormFill_SecondChange) {
CreateTestProfile();
GURL url = embedded_test_server()->GetURL(
"a.com", "/autofill/double_dynamic_form.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
ValueWaiter refill = ListenForRefill("firstname_form2");
ASSERT_TRUE(AutofillFlow(GetElementById("firstname"), this));
AdvanceClock(kLessThanLimitBeforeRefill);
ASSERT_FALSE(std::move(refill).Wait());
// Make sure the new form was not filled.
EXPECT_EQ("", GetFieldValueById("firstname_form2"));
EXPECT_EQ("", GetFieldValueById("address_form2"));
EXPECT_EQ("CA", GetFieldValueById("state_form2")); // Default value.
EXPECT_EQ("", GetFieldValueById("city_form2"));
EXPECT_EQ("", GetFieldValueById("company_form2"));
EXPECT_EQ("", GetFieldValueById("email_form2"));
EXPECT_EQ("", GetFieldValueById("phone_form2"));
}
// Test that forms that dynamically change after a second do not get filled.
IN_PROC_BROWSER_TEST_P(AutofillInteractiveTestDynamicForm,
DynamicChangingFormFill_AfterDelay) {
CreateTestProfile();
GURL url = embedded_test_server()->GetURL(
"a.com", "/autofill/dynamic_form_after_delay.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
ValueWaiter refill = ListenForRefill("firstname_form1");
ASSERT_TRUE(AutofillFlow(GetElementById("firstname"), this));
AdvanceClock(kLimitBeforeRefill + base::Milliseconds(1));
ASSERT_FALSE(std::move(refill).Wait());
// Make sure that the new form was not filled.
EXPECT_EQ("", GetFieldValueById("firstname_form1"));
EXPECT_EQ("", GetFieldValueById("address_form1"));
EXPECT_EQ("CA", GetFieldValueById("state_form1")); // Default value.
EXPECT_EQ("", GetFieldValueById("city_form1"));
EXPECT_EQ("", GetFieldValueById("company_form1"));
EXPECT_EQ("", GetFieldValueById("email_form1"));
EXPECT_EQ("", GetFieldValueById("phone_form1"));
}
// Test that only field of a type group that was filled initially get refilled.
// TODO(https://crbug.com/1297560): Check back if flakiness is fixed now.
IN_PROC_BROWSER_TEST_P(AutofillInteractiveTestDynamicForm,
DynamicChangingFormFill_AddsNewFieldTypeGroups) {
CreateTestProfile();
GURL url = embedded_test_server()->GetURL(
"a.com", "/autofill/dynamic_form_new_field_types.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
ValueWaiter refill = ListenForRefill("firstname_form1");
ASSERT_TRUE(AutofillFlow(GetElementById("firstname"), this));
AdvanceClock(kLessThanLimitBeforeRefill);
ASSERT_TRUE(std::move(refill).Wait());
// The fields present in the initial fill should be filled.
EXPECT_EQ("Milton", GetFieldValueById("firstname_form1"));
EXPECT_EQ("4120 Freidrich Lane", GetFieldValueById("address_form1"));
EXPECT_EQ("TX", GetFieldValueById("state_form1"));
EXPECT_EQ("Austin", GetFieldValueById("city_form1"));
// Fields from group that were not present in the initial fill should not be
// filled
EXPECT_EQ("", GetFieldValueById("company_form1"));
// Fields that were present but hidden in the initial fill should not be
// filled.
EXPECT_EQ("", GetFieldValueById("email_form1"));
// The phone should be filled even if it's a different format than the initial
// fill.
EXPECT_EQ("5125551234", GetFieldValueById("phone_form1"));
}
// Test that we can autofill forms that dynamically change select fields to text
// fields by changing the visibilities.
IN_PROC_BROWSER_TEST_P(AutofillInteractiveTestDynamicForm,
DynamicFormFill_SelectToText) {
CreateTestProfile();
GURL url = embedded_test_server()->GetURL(
"a.com", "/autofill/dynamic_form_select_to_text.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
ValueWaiter refill = ListenForRefill("firstname");
ASSERT_TRUE(AutofillFlow(GetElementById("firstname"), this));
AdvanceClock(kLessThanLimitBeforeRefill);
ASSERT_TRUE(std::move(refill).Wait());
// Make sure the new form was filled correctly.
EXPECT_EQ("Milton", GetFieldValueById("firstname"));
EXPECT_EQ("4120 Freidrich Lane", GetFieldValueById("address1"));
EXPECT_EQ("Texas", GetFieldValueById("state_us"));
EXPECT_EQ("Austin", GetFieldValueById("city"));
EXPECT_EQ("Initech", GetFieldValueById("company"));
EXPECT_EQ("red.swingline@initech.com", GetFieldValueById("email"));
EXPECT_EQ("15125551234", GetFieldValueById("phone"));
}
// Test that we can autofill forms that dynamically change the visibility of a
// field after it's autofilled.
// TODO(https://crbug.com/1297560): Check back if flakiness is fixed now.
IN_PROC_BROWSER_TEST_P(AutofillInteractiveTestDynamicForm,
DynamicFormFill_VisibilitySwitch) {
CreateTestProfile();
GURL url = embedded_test_server()->GetURL(
"a.com", "/autofill/dynamic_form_visibility_switch.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
ValueWaiter refill = ListenForRefill("firstname");
ASSERT_TRUE(AutofillFlow(GetElementById("firstname"), this));
AdvanceClock(kLessThanLimitBeforeRefill);
ASSERT_TRUE(std::move(refill).Wait());
// Make sure the new form was filled correctly.
EXPECT_EQ("Milton", GetFieldValueById("firstname"));
EXPECT_EQ("4120 Freidrich Lane", GetFieldValueById("address1"));
// Both fields must be filled after a refill.
EXPECT_EQ("Texas", GetFieldValueById("state_first"));
EXPECT_EQ("Texas", GetFieldValueById("state_second"));
EXPECT_EQ("Austin", GetFieldValueById("city"));
EXPECT_EQ("Initech", GetFieldValueById("company"));
EXPECT_EQ("red.swingline@initech.com", GetFieldValueById("email"));
EXPECT_EQ("15125551234", GetFieldValueById("phone"));
}
// Test that we can autofill forms that dynamically change the element that
// has been clicked on.
IN_PROC_BROWSER_TEST_P(AutofillInteractiveTestDynamicForm,
DynamicFormFill_FirstElementDisappears) {
CreateTestProfile();
GURL url = embedded_test_server()->GetURL(
"a.com", "/autofill/dynamic_form_element_invalid.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
ValueWaiter refill = ListenForRefill("address1");
ASSERT_TRUE(AutofillFlow(GetElementById("firstname"), this));
AdvanceClock(kLessThanLimitBeforeRefill);
ASSERT_TRUE(std::move(refill).Wait());
// Make sure the new form was filled correctly.
EXPECT_EQ("Milton", GetFieldValueById("firstname2"));
EXPECT_EQ("4120 Freidrich Lane", GetFieldValueById("address1"));
EXPECT_EQ("Austin", GetFieldValueById("city"));
EXPECT_EQ("Initech", GetFieldValueById("company"));
EXPECT_EQ("red.swingline@initech.com", GetFieldValueById("email"));
EXPECT_EQ("15125551234", GetFieldValueById("phone"));
}
// Test that we can autofill forms that dynamically change the element that
// has been clicked on, even though the form has no name.
// TODO(https://crbug.com/1297560): Check back if flakiness is fixed now.
IN_PROC_BROWSER_TEST_P(AutofillInteractiveTestDynamicForm,
DynamicFormFill_FirstElementDisappearsNoNameForm) {
CreateTestProfile();
GURL url = embedded_test_server()->GetURL(
"a.com", "/autofill/dynamic_form_element_invalid_noname_form.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
ValueWaiter refill = ListenForRefill("address1");
ASSERT_TRUE(AutofillFlow(GetElementById("firstname"), this));
AdvanceClock(kLessThanLimitBeforeRefill);
ASSERT_TRUE(std::move(refill).Wait());
// Make sure the new form was filled correctly.
EXPECT_EQ("Milton", GetFieldValueById("firstname2"));
EXPECT_EQ("4120 Freidrich Lane", GetFieldValueById("address1"));
EXPECT_EQ("Austin", GetFieldValueById("city"));
EXPECT_EQ("Initech", GetFieldValueById("company"));
EXPECT_EQ("red.swingline@initech.com", GetFieldValueById("email"));
EXPECT_EQ("15125551234", GetFieldValueById("phone"));
}
// Test that we can autofill forms that dynamically change the element that
// has been clicked on, even though there are multiple forms with identical
// names.
IN_PROC_BROWSER_TEST_P(
AutofillInteractiveTestDynamicForm,
DynamicFormFill_FirstElementDisappearsMultipleBadNameForms) {
CreateTestProfile();
GURL url = embedded_test_server()->GetURL(
"a.com",
"/autofill/dynamic_form_element_invalid_multiple_badname_forms.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
ValueWaiter refill = ListenForRefill("address1_7");
ASSERT_TRUE(AutofillFlow(GetElementById("firstname_5"), this));
AdvanceClock(kLessThanLimitBeforeRefill);
ASSERT_TRUE(std::move(refill).Wait());
// Make sure the second form was filled correctly, and the first form was left
// unfilled.
EXPECT_EQ("", GetFieldValueById("firstname_1"));
EXPECT_EQ("", GetFieldValueById("firstname_2"));
EXPECT_EQ("", GetFieldValueById("address1_3"));
EXPECT_EQ("CA", GetFieldValueById("country_4")); // default
EXPECT_EQ("Milton", GetFieldValueById("firstname_6"));
EXPECT_EQ("4120 Freidrich Lane", GetFieldValueById("address1_7"));
EXPECT_EQ("US", GetFieldValueById("country_8"));
}
// Test that we can autofill forms that dynamically change the element that
// has been clicked on, even though there are multiple forms with identical
// names.
IN_PROC_BROWSER_TEST_P(AutofillInteractiveTestDynamicForm,
DynamicFormFill_FirstElementDisappearsBadnameUnowned) {
CreateTestProfile();
GURL url = embedded_test_server()->GetURL(
"a.com", "/autofill/dynamic_form_element_invalid_unowned_badnames.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
ValueWaiter refill = ListenForRefill("address1_7");
ASSERT_TRUE(AutofillFlow(GetElementById("firstname_5"), this));
AdvanceClock(kLessThanLimitBeforeRefill);
ASSERT_TRUE(std::move(refill).Wait());
// Make sure the second form was filled correctly, and the first form was left
// unfilled.
EXPECT_EQ("", GetFieldValueById("firstname_1"));
EXPECT_EQ("", GetFieldValueById("firstname_2"));
EXPECT_EQ("", GetFieldValueById("address1_3"));
EXPECT_EQ("CA", GetFieldValueById("country_4")); // default
EXPECT_EQ("Milton", GetFieldValueById("firstname_6"));
EXPECT_EQ("4120 Freidrich Lane", GetFieldValueById("address1_7"));
EXPECT_EQ("US", GetFieldValueById("country_8"));
}
// Test that we can autofill forms that dynamically change the element that
// has been clicked on, even though there are multiple forms with no name.
// TODO(https://crbug.com/1297560): Check back if flakiness is fixed now.
IN_PROC_BROWSER_TEST_P(
AutofillInteractiveTestDynamicForm,
DynamicFormFill_FirstElementDisappearsMultipleNoNameForms) {
CreateTestProfile();
GURL url = embedded_test_server()->GetURL(
"a.com",
"/autofill/dynamic_form_element_invalid_multiple_noname_forms.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
ValueWaiter refill = ListenForRefill("address1_7");
ASSERT_TRUE(AutofillFlow(GetElementById("firstname_5"), this));
AdvanceClock(kLessThanLimitBeforeRefill);
ASSERT_TRUE(std::move(refill).Wait());
// Make sure the second form was filled correctly, and the first form was left
// unfilled.
EXPECT_EQ("", GetFieldValueById("firstname_1"));
EXPECT_EQ("", GetFieldValueById("firstname_2"));
EXPECT_EQ("", GetFieldValueById("address1_3"));
EXPECT_EQ("CA", GetFieldValueById("country_4")); // default
EXPECT_EQ("Milton", GetFieldValueById("firstname_6"));
EXPECT_EQ("4120 Freidrich Lane", GetFieldValueById("address1_7"));
EXPECT_EQ("US", GetFieldValueById("country_8"));
}
// Test that we can autofill forms that dynamically change the element that
// has been clicked on, even though the elements are unowned.
IN_PROC_BROWSER_TEST_P(AutofillInteractiveTestDynamicForm,
DynamicFormFill_FirstElementDisappearsUnowned) {
CreateTestProfile();
GURL url = embedded_test_server()->GetURL(
"a.com", "/autofill/dynamic_form_element_invalid_unowned.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
ValueWaiter refill = ListenForRefill("address1");
ASSERT_TRUE(AutofillFlow(GetElementById("firstname"), this));
AdvanceClock(kLessThanLimitBeforeRefill);
ASSERT_TRUE(std::move(refill).Wait());
// Make sure the new form was filled correctly.
EXPECT_EQ("Milton", GetFieldValueById("firstname2"));
EXPECT_EQ("4120 Freidrich Lane", GetFieldValueById("address1"));
EXPECT_EQ("Austin", GetFieldValueById("city"));
EXPECT_EQ("Initech", GetFieldValueById("company"));
EXPECT_EQ("red.swingline@initech.com", GetFieldValueById("email"));
EXPECT_EQ("15125551234", GetFieldValueById("phone"));
}
// Test that credit card fields are re-filled.
IN_PROC_BROWSER_TEST_P(AutofillInteractiveTestDynamicForm,
DynamicChangingFormFill_AlsoForCreditCard) {
CreateTestCreditCart();
GURL url = https_server()->GetURL("a.com",
"/autofill/dynamic_form_credit_card.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
ValueWaiter refill = ListenForRefill("cc-name");
ASSERT_TRUE(AutofillFlow(GetElementById("cc-name"), this,
{.show_method = ShowMethod::ByChar('M')}));
AdvanceClock(kLessThanLimitBeforeRefill);
ASSERT_TRUE(std::move(refill).Wait());
EXPECT_EQ("Milton Waddams", GetFieldValueById("cc-name"));
EXPECT_EQ("4111111111111111", GetFieldValueById("cc-num"));
EXPECT_EQ("09", GetFieldValueById("cc-exp-month"));
EXPECT_EQ("2999", GetFieldValueById("cc-exp-year"));
EXPECT_EQ("", GetFieldValueById("cc-csc"));
}
// Test that we can Autofill dynamically changing selects that have options
// added and removed.
IN_PROC_BROWSER_TEST_P(AutofillInteractiveTestDynamicForm,
DynamicChangingFormFill_SelectUpdated) {
CreateTestProfile();
GURL url = embedded_test_server()->GetURL(
"a.com", "/autofill/dynamic_form_select_options_change.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
ValueWaiter refill = ListenForRefill("firstname");
ASSERT_TRUE(AutofillFlow(GetElementById("firstname"), this));
AdvanceClock(kLessThanLimitBeforeRefill);
ASSERT_TRUE(std::move(refill).Wait());
// Make sure the new form was filled correctly.
EXPECT_EQ("Milton", GetFieldValueById("firstname"));
EXPECT_EQ("4120 Freidrich Lane", GetFieldValueById("address1"));
EXPECT_EQ("TX", GetFieldValueById("state"));
EXPECT_EQ("Austin", GetFieldValueById("city"));
EXPECT_EQ("Initech", GetFieldValueById("company"));
EXPECT_EQ("red.swingline@initech.com", GetFieldValueById("email"));
EXPECT_EQ("15125551234", GetFieldValueById("phone"));
}
// Test that we can Autofill dynamically changing selects that have options
// added and removed only once.
IN_PROC_BROWSER_TEST_P(AutofillInteractiveTestDynamicForm,
DynamicChangingFormFill_DoubleSelectUpdated) {
CreateTestProfile();
GURL url = embedded_test_server()->GetURL(
"a.com", "/autofill/dynamic_form_double_select_options_change.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
ValueWaiter refill1 = ListenForRefill("address1", "refill1");
ValueWaiter refill2 = ListenForRefill("firstname", "refill2");
ASSERT_TRUE(AutofillFlow(GetElementById("firstname"), this));
AdvanceClock(kLessThanLimitBeforeRefill);
ASSERT_TRUE(std::move(refill1).Wait());
ASSERT_FALSE(std::move(refill2).Wait());
// Upon the first fill, JS resets the address1 field, which triggers a refill.
// Upon the refill, JS resets the T
EXPECT_EQ("", GetFieldValueById(
"firstname")); // That field value was reset dynamically.
EXPECT_EQ("4120 Freidrich Lane", GetFieldValueById("address1"));
EXPECT_EQ("CA", GetFieldValueById("state")); // The <select>'s default value.
EXPECT_EQ("Austin", GetFieldValueById("city"));
EXPECT_EQ("Initech", GetFieldValueById("company"));
EXPECT_EQ("red.swingline@initech.com", GetFieldValueById("email"));
EXPECT_EQ("15125551234", GetFieldValueById("phone"));
}
// Test that we can Autofill dynamically generated forms with no name if the
// NameForAutofill of the first field matches.
IN_PROC_BROWSER_TEST_P(AutofillInteractiveTestDynamicForm,
DynamicChangingFormFill_FormWithoutName) {
CreateTestProfile();
GURL url = embedded_test_server()->GetURL(
"a.com", "/autofill/dynamic_form_no_name.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
ValueWaiter refill = ListenForRefill("firstname_form1");
ASSERT_TRUE(AutofillFlow(GetElementById("firstname"), this));
AdvanceClock(kLessThanLimitBeforeRefill);
ASSERT_TRUE(std::move(refill).Wait());
// Make sure the new form was filled correctly.
EXPECT_EQ("Milton", GetFieldValueById("firstname_form1"));
EXPECT_EQ("4120 Freidrich Lane", GetFieldValueById("address_form1"));
EXPECT_EQ("TX", GetFieldValueById("state_form1"));
EXPECT_EQ("Austin", GetFieldValueById("city_form1"));
EXPECT_EQ("Initech", GetFieldValueById("company_form1"));
EXPECT_EQ("red.swingline@initech.com", GetFieldValueById("email_form1"));
EXPECT_EQ("15125551234", GetFieldValueById("phone_form1"));
}
// Test that we can Autofill dynamically changing selects that have options
// added and removed for forms with no names if the NameForAutofill of the first
// field matches.
// TODO(https://crbug.com/1297560): Check back if flakiness is fixed now.
IN_PROC_BROWSER_TEST_P(AutofillInteractiveTestDynamicForm,
DynamicChangingFormFill_SelectUpdated_FormWithoutName) {
CreateTestProfile();
GURL url = embedded_test_server()->GetURL(
"a.com",
"/autofill/dynamic_form_with_no_name_select_options_change.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
ValueWaiter refill = ListenForRefill("firstname");
ASSERT_TRUE(AutofillFlow(GetElementById("firstname"), this));
AdvanceClock(kLessThanLimitBeforeRefill);
ASSERT_TRUE(std::move(refill).Wait());
// Make sure the new form was filled correctly.
EXPECT_EQ("Milton", GetFieldValueById("firstname"));
EXPECT_EQ("4120 Freidrich Lane", GetFieldValueById("address1"));
EXPECT_EQ("TX", GetFieldValueById("state"));
EXPECT_EQ("Austin", GetFieldValueById("city"));
EXPECT_EQ("Initech", GetFieldValueById("company"));
EXPECT_EQ("red.swingline@initech.com", GetFieldValueById("email"));
EXPECT_EQ("15125551234", GetFieldValueById("phone"));
}
// Test that we can Autofill dynamically generated synthetic forms if the
// NameForAutofill of the first field matches.
// TODO(https://crbug.com/1297560): Check back if flakiness is fixed now.
IN_PROC_BROWSER_TEST_P(AutofillInteractiveTestDynamicForm,
DynamicChangingFormFill_SyntheticForm) {
CreateTestProfile();
GURL url = embedded_test_server()->GetURL(
"a.com", "/autofill/dynamic_synthetic_form.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
ValueWaiter refill = ListenForRefill("firstname_syntheticform1");
ASSERT_TRUE(AutofillFlow(GetElementById("firstname"), this));
AdvanceClock(kLessThanLimitBeforeRefill);
ASSERT_TRUE(std::move(refill).Wait());
// Make sure the new form was filled correctly.
EXPECT_EQ("Milton", GetFieldValueById("firstname_syntheticform1"));
EXPECT_EQ("4120 Freidrich Lane", GetFieldValueById("address_syntheticform1"));
EXPECT_EQ("TX", GetFieldValueById("state_syntheticform1"));
EXPECT_EQ("Austin", GetFieldValueById("city_syntheticform1"));
EXPECT_EQ("Initech", GetFieldValueById("company_syntheticform1"));
EXPECT_EQ("red.swingline@initech.com",
GetFieldValueById("email_syntheticform1"));
EXPECT_EQ("15125551234", GetFieldValueById("phone_syntheticform1"));
}
// Test that we can Autofill dynamically synthetic forms when the select options
// change if the NameForAutofill of the first field matches
// TODO(https://crbug.com/1297560): Check back if flakiness is fixed now.
IN_PROC_BROWSER_TEST_P(AutofillInteractiveTestDynamicForm,
DynamicChangingFormFill_SelectUpdated_SyntheticForm) {
CreateTestProfile();
GURL url = embedded_test_server()->GetURL(
"a.com", "/autofill/dynamic_synthetic_form_select_options_change.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
ValueWaiter refill = ListenForRefill("firstname");
ASSERT_TRUE(AutofillFlow(GetElementById("firstname"), this));
AdvanceClock(kLessThanLimitBeforeRefill);
ASSERT_TRUE(std::move(refill).Wait());
// Make sure the new form was filled correctly.
EXPECT_EQ("Milton", GetFieldValueById("firstname"));
EXPECT_EQ("4120 Freidrich Lane", GetFieldValueById("address1"));
EXPECT_EQ("TX", GetFieldValueById("state"));
EXPECT_EQ("Austin", GetFieldValueById("city"));
EXPECT_EQ("Initech", GetFieldValueById("company"));
EXPECT_EQ("red.swingline@initech.com", GetFieldValueById("email"));
EXPECT_EQ("15125551234", GetFieldValueById("phone"));
}
// Some websites have JavaScript handlers that mess with the input of the user
// and autofill. A common problem is that the date "09/2999" gets reformatted
// into "09 / 20" instead of "09 / 99".
// In these tests, the following steps will happen:
// 1) Autofill recognizes an expiration date field with maxlength=7, will infer
// that it is supposed to fill 09/2999 and will fill that value.
// 2) The website sees the content 09/2999 and reformats it to 09 / 29 because
// this is what websites do sometimes.
// 3) The AutofillAgent recognizes that it failed to fill 09/2999 and fills
// 09 / 99 instead.
// 4) The promise waits to see 09 / 99 and resolved.
// Flaky on Win https://crbug.com/1337757.
IN_PROC_BROWSER_TEST_P(AutofillInteractiveTestDynamicForm,
FillCardOnReformattingForm) {
CreateTestCreditCart();
GURL url = https_server()->GetURL(
"a.com", "/autofill/autofill_creditcard_form_with_date_formatter.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
ValueWaiter reformat_waiter =
ListenForValueChange("CREDIT_CARD_EXP_DATE", "unblock", GetWebContents());
ASSERT_TRUE(AutofillFlow(GetElementById("CREDIT_CARD_NAME_FULL"), this));
AdvanceClock(kLessThanLimitBeforeRefill);
ASSERT_TRUE(std::move(reformat_waiter).Wait());
EXPECT_EQ("09 / 99", GetFieldValue(GetElementById("CREDIT_CARD_EXP_DATE")));
// The timestamp from BrowserAutofillManager::OnDidFillAutofillFormData()
// comes from the renderer process and thus from an actual clock. Since this
// interaction timestamp must be before the submission timestamp, we advance
// the browser by a lot.
AdvanceClock(base::Minutes(10));
content::LoadStopObserver load_stop_observer(GetWebContents());
ASSERT_TRUE(content::ExecuteScript(
GetWebContents(), "document.getElementById('testform').submit();"));
load_stop_observer.Wait();
// Short hand for ExpectbucketCount:
auto expect_count = [&](base::StringPiece name,
base::HistogramBase::Sample sample,
base::HistogramBase::Count expected_count) {
histogram_tester().ExpectBucketCount(name, sample, expected_count);
};
expect_count("Autofill.KeyMetrics.FillingReadiness.CreditCard", 1, 1);
expect_count("Autofill.KeyMetrics.FillingAcceptance.CreditCard", 1, 1);
expect_count("Autofill.KeyMetrics.FillingCorrectness.CreditCard", 1, 1);
expect_count("Autofill.KeyMetrics.FillingAssistance.CreditCard", 1, 1);
// Ensure that refills don't count as edits.
expect_count("Autofill.NumberOfEditedAutofilledFieldsAtSubmission", 0, 1);
expect_count("Autofill.PerfectFilling.CreditCards", 1, 1);
// Bucket 0 = edited, 1 = accepted; 3 samples for 3 fields.
expect_count("Autofill.EditedAutofilledFieldAtSubmission.Aggregate", 0, 0);
expect_count("Autofill.EditedAutofilledFieldAtSubmission.Aggregate", 1, 3);
}
IN_PROC_BROWSER_TEST_F(AutofillInteractiveTest, ShadowDOM) {
CreateTestProfile();
GURL url =
embedded_test_server()->GetURL("a.com", "/autofill/shadowdom.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
ASSERT_TRUE(AutofillFlow(ElementExpr("getNameElement()"), this));
auto Js = [this](const std::string& code) {
return content::EvalJs(GetWebContents(), code);
};
EXPECT_EQ("Milton C. Waddams", Js("getName()"));
EXPECT_EQ("4120 Freidrich Lane", Js("getAddress()"));
EXPECT_EQ("Austin", Js("getCity()"));
EXPECT_EQ("TX", Js("getState()"));
EXPECT_EQ("78744", Js("getZip()"));
}
IN_PROC_BROWSER_TEST_F(AutofillInteractiveTest, ShadowDOMNoInference) {
CreateTestProfile();
GURL url = embedded_test_server()->GetURL(
"a.com", "/autofill/shadowdom-no-inference.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
ASSERT_TRUE(AutofillFlow(ElementExpr("getNameElement()"), this));
auto Js = [this](const std::string& code) {
return content::EvalJs(GetWebContents(), code);
};
EXPECT_EQ("Milton C. Waddams", Js("getName()"));
EXPECT_EQ("4120 Freidrich Lane", Js("getAddress()"));
EXPECT_EQ("TX", Js("getState()"));
}
// ChromeVox is only available on ChromeOS.
#if BUILDFLAG(IS_CHROMEOS_ASH) && BUILDFLAG(ENABLE_EXTENSIONS)
class AutofillInteractiveTestChromeVox : public AutofillInteractiveTestBase {
public:
AutofillInteractiveTestChromeVox() = default;
~AutofillInteractiveTestChromeVox() override = default;
void TearDownOnMainThread() override {
// Unload the ChromeVox extension so the browser doesn't try to respond to
// in-flight requests during test shutdown. https://crbug.com/923090
ash::AccessibilityManager::Get()->EnableSpokenFeedback(false);
AutomationManagerAura::GetInstance()->Disable();
AutofillInteractiveTestBase::TearDownOnMainThread();
}
void EnableChromeVox() {
// Test setup.
// Enable ChromeVox, disable earcons and wait for key mappings to be
// fetched.
ASSERT_FALSE(ash::AccessibilityManager::Get()->IsSpokenFeedbackEnabled());
// TODO(accessibility): fix console error/warnings and insantiate
// |console_observer_| here.
// Load ChromeVox and block until it's fully loaded.
ash::AccessibilityManager::Get()->EnableSpokenFeedback(true);
sm_.ExpectSpeechPattern("*");
sm_.Call([this]() { DisableEarcons(); });
}
void DisableEarcons() {
// Playing earcons from within a test is not only annoying if you're
// running the test locally, but seems to cause crashes
// (http://crbug.com/396507). Work around this by just telling
// ChromeVox to not ever play earcons (prerecorded sound effects).
extensions::browsertest_util::ExecuteScriptInBackgroundPageNoWait(
browser()->profile(), extension_misc::kChromeVoxExtensionId,
"ChromeVox.earcons.playEarcon = function() {};");
}
ash::test::SpeechMonitor sm_;
};
// Ensure that autofill suggestions are properly read out via ChromeVox.
// This is a regressions test for crbug.com/1208913.
// TODO(https://crbug.com/1294266): Flaky on ChromeOS
#if BUILDFLAG(IS_CHROMEOS)
#define MAYBE_TestNotificationOfAutofillDropdown \
DISABLED_TestNotificationOfAutofillDropdown
#else
#define MAYBE_TestNotificationOfAutofillDropdown \
TestNotificationOfAutofillDropdown
#endif
IN_PROC_BROWSER_TEST_F(AutofillInteractiveTestChromeVox,
MAYBE_TestNotificationOfAutofillDropdown) {
CreateTestProfile();
SetTestUrlResponse(kTestShippingFormString);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetTestUrl()));
EnableChromeVox();
content::EnableAccessibilityForWebContents(web_contents());
// The following contains a sequence of calls to
// sm_.ExpectSpeechPattern() and test_delegate()->Wait(). It is essential
// to first flush the expected speech patterns, otherwise the two functions
// start incompatible RunLoops.
sm_.ExpectSpeechPattern("Web Content");
sm_.Call([this]() {
content::WaitForAccessibilityTreeToContainNodeWithName(web_contents(),
"First name:");
web_contents()->Focus();
test_delegate()->SetExpectations({ObservedUiEvents::kSuggestionShown});
ASSERT_TRUE(FocusField(GetElementById("firstname"), GetWebContents()));
});
sm_.ExpectSpeechPattern("First name:");
sm_.ExpectSpeechPattern("Edit text");
sm_.ExpectSpeechPattern("Region");
// Wait for suggestions popup to show up. This needs to happen before we
// simulate the cursor down key press.
sm_.Call([this]() { test_delegate()->Wait(); });
sm_.Call([this]() {
test_delegate()->SetExpectations({ObservedUiEvents::kPreviewFormData});
ASSERT_TRUE(
ui_controls::SendKeyPress(browser()->window()->GetNativeWindow(),
ui::VKEY_DOWN, false, false, false, false));
});
sm_.ExpectSpeechPattern("Autofill menu opened");
sm_.ExpectSpeechPattern("Milton 4120 Freidrich Lane");
sm_.ExpectSpeechPattern("List item");
sm_.ExpectSpeechPattern("1 of 2");
sm_.Call([this]() { test_delegate()->Wait(); });
sm_.Replay();
}
#endif // BUILDFLAG(IS_CHROMEOS_ASH) && BUILDFLAG(ENABLE_EXTENSIONS)
// These tests are disabled on LaCros because <select> elements don't listen
// to typed characters the same way as other platforms. Sending the characters
// 'W', 'A' while the state selector is focused does not trigger a selection
// of the entry "WA".
#if BUILDFLAG(IS_CHROMEOS_LACROS)
#define MAYBE_AutofillInteractiveFormSubmissionTest \
DISABLED_AutofillInteractiveFormSubmissionTest
#else
#define MAYBE_AutofillInteractiveFormSubmissionTest \
AutofillInteractiveFormSubmissionTest
#endif
class MAYBE_AutofillInteractiveFormSubmissionTest
: public AutofillInteractiveTestBase {
public:
class MockAutofillManager : public BrowserAutofillManager {
public:
MockAutofillManager(ContentAutofillDriver* driver, AutofillClient* client)
: BrowserAutofillManager(driver, client, "en-US") {}
MOCK_METHOD(void,
OnFormSubmittedImpl,
(const FormData&, bool, mojom::SubmissionSource),
(override));
};
MockAutofillManager* autofill_manager() {
return autofill_manager(GetWebContents()->GetPrimaryMainFrame());
}
MockAutofillManager* autofill_manager(content::RenderFrameHost* rfh) {
return autofill_manager_injector_[rfh];
}
void SetUpOnMainThread() override {
AutofillInteractiveTestBase::SetUpOnMainThread();
SetUpServer();
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetTestUrl()));
ASSERT_TRUE(WaitForMatchingForm(
autofill_manager(), base::BindRepeating([](const FormStructure& form) {
return form.active_field_count() == 5;
})));
EnterValues();
}
void SetUpServer() {
SetTestUrlResponse(R"(
<html><body>
<form id='form' method='POST' action='/success.html'>
Name: <input type='text' id='name'><br>
Address: <input type='text' id='address'><br>
City: <input type='text' id='city'><br>
ZIP: <input type='text' id='zip'><br>
State: <select id='state'>
<option value='CA'>CA</option>
<option value='WA'>WA</option>
</select><br>
</form>
)");
SetResponseForUrlPath("/success.html", "<html><body>Happy times!");
SetResponseForUrlPath("/xhr", "<foo>Happy times!</foo>");
}
void EnterValues() {
TestAutofillManagerWaiter waiter(
*autofill_manager(), {AutofillManagerEvent::kTextFieldDidChange,
AutofillManagerEvent::kTextFieldDidChange});
// Normally we would enter the state last, but we don't have a
// kSelectElementDidChange event, yet. Therefore, we just wait until
// the second text field was reported to the autofill manager.
ASSERT_TRUE(
EnterTextsIntoFields({{GetElementById("name"), "Sarah"},
{GetElementById("state"), "WA"},
{GetElementById("address"), "123 Main Road"}},
this, GetWebContents()));
ASSERT_TRUE(waiter.Wait(2u));
}
std::map<std::u16string, std::u16string> GetExpectedValues() {
return std::map<std::u16string, std::u16string>{
{u"name", u"Sarah"},
{u"address", u"123 Main Road"},
{u"city", u""},
{u"zip", u""},
{u"state", u"WA"}};
}
void ExecuteScript(const std::string& script) {
ASSERT_TRUE(content::ExecJs(GetWebContents(), script));
}
private:
TestAutofillManagerInjector<MockAutofillManager> autofill_manager_injector_;
};
// Tests that user-triggered submission triggers a submission event in
// BrowserAutofillManager.
IN_PROC_BROWSER_TEST_F(MAYBE_AutofillInteractiveFormSubmissionTest,
Submission) {
base::RunLoop run_loop;
// Ensure that only expected form submissions are recorded.
EXPECT_CALL(*autofill_manager(), OnFormSubmittedImpl).Times(0);
EXPECT_CALL(*autofill_manager(),
OnFormSubmittedImpl(SubmittedValuesAre(GetExpectedValues()),
/*known_success=*/false,
mojom::SubmissionSource::FORM_SUBMISSION))
.Times(1)
.WillRepeatedly(
testing::InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
ExecuteScript("document.getElementById('form').submit();");
run_loop.Run();
}
// Tests that non-link-click, renderer-inititiated navigation triggers a
// submission event in BrowserAutofillManager.
IN_PROC_BROWSER_TEST_F(MAYBE_AutofillInteractiveFormSubmissionTest,
ProbableSubmission) {
base::RunLoop run_loop;
// Ensure that only expected form submissions are recorded.
EXPECT_CALL(*autofill_manager(), OnFormSubmittedImpl).Times(0);
EXPECT_CALL(
*autofill_manager(),
OnFormSubmittedImpl(SubmittedValuesAre(GetExpectedValues()),
/*known_success=*/false,
mojom::SubmissionSource::PROBABLY_FORM_SUBMITTED))
.Times(1)
.WillRepeatedly(
testing::InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
// Add a delay before navigating away to avoid race conditions. This is
// appropriate since we're faking user interaction here.
ExecuteScript(
"setTimeout(() => { window.location.assign('/success.html'); }, 50);");
run_loop.Run();
}
// Tests that a same document navigation can trigger a form submission.
IN_PROC_BROWSER_TEST_F(MAYBE_AutofillInteractiveFormSubmissionTest,
SameDocumentNavigation) {
base::RunLoop run_loop;
// Ensure that only expected form submissions are recorded.
EXPECT_CALL(*autofill_manager(), OnFormSubmittedImpl).Times(0);
EXPECT_CALL(
*autofill_manager(),
OnFormSubmittedImpl(SubmittedValuesAre(GetExpectedValues()),
/*known_success=*/true,
mojom::SubmissionSource::SAME_DOCUMENT_NAVIGATION))
.Times(1)
.WillRepeatedly(
testing::InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
// Simulate form submission.
ExecuteScript(
R"(
// Same document navigation:
document.getElementById('form').style.display = 'none';
const url = new URL(window.location);
url.searchParams.set('foo', 'bar');
window.history.pushState({}, '', url);
// Hide form, which is the trigger for the submission event.
document.getElementById('form').style.display = 'none';
)");
run_loop.Run();
}
// Tests that an XHR request can indicate a form submission.
IN_PROC_BROWSER_TEST_F(MAYBE_AutofillInteractiveFormSubmissionTest,
XhrSuccededAndHideForm) {
base::RunLoop run_loop;
// Ensure that only expected form submissions are recorded.
EXPECT_CALL(*autofill_manager(), OnFormSubmittedImpl).Times(0);
EXPECT_CALL(*autofill_manager(),
OnFormSubmittedImpl(SubmittedValuesAre(GetExpectedValues()),
/*known_success=*/true,
mojom::SubmissionSource::XHR_SUCCEEDED))
.Times(1)
.WillRepeatedly(
testing::InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
// Simulate form submission.
ExecuteScript(
R"(
// SubmissionSource::XHR_SUCCEEDED is triggered if an XHR is observed
// after the form has been made invisible.
document.getElementById('form').style.display = 'none';
const xhr = new XMLHttpRequest();
xhr.open('GET', '/xhr', true);
xhr.send(null);
)");
run_loop.Run();
}
// Tests that an XHR request can indicate a form submission - even if the form
// is deleted from the DOM.
IN_PROC_BROWSER_TEST_F(MAYBE_AutofillInteractiveFormSubmissionTest,
XhrSuccededAndDeleteForm) {
base::RunLoop run_loop;
// Ensure that only expected form submissions are recorded.
EXPECT_CALL(*autofill_manager(), OnFormSubmittedImpl).Times(0);
EXPECT_CALL(*autofill_manager(),
OnFormSubmittedImpl(SubmittedValuesAre(GetExpectedValues()),
/*known_success=*/true,
mojom::SubmissionSource::XHR_SUCCEEDED))
.Times(1)
.WillRepeatedly(
testing::InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
// Simulate form submission.
ExecuteScript(
R"(
// SubmissionSource::XHR_SUCCEEDED is triggered if an XHR is observed
// after the form has been deleted.
const form = document.getElementById('form');
form.remove();
const xhr = new XMLHttpRequest();
xhr.open('GET', '/xhr', true);
xhr.send(null);
)");
run_loop.Run();
}
// Tests that a DOM mutation after an XHR can indicate a form submission.
IN_PROC_BROWSER_TEST_F(MAYBE_AutofillInteractiveFormSubmissionTest,
DomMutationAfterXhr) {
base::RunLoop run_loop;
// Ensure that only expected form submissions are recorded.
EXPECT_CALL(*autofill_manager(), OnFormSubmittedImpl).Times(0);
EXPECT_CALL(
*autofill_manager(),
OnFormSubmittedImpl(SubmittedValuesAre(GetExpectedValues()),
/*known_success=*/true,
mojom::SubmissionSource::DOM_MUTATION_AFTER_XHR))
.Times(1)
.WillRepeatedly(
testing::InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
// Simulate form submission.
ExecuteScript(
R"(
const xhr = new XMLHttpRequest();
xhr.open('GET', '/xhr', true);
xhr.onload = () => {
// SubmissionSource::DOM_MUTATION_AFTER_XHR is triggered if a form
// is hidden an XHR was observed.
// The DOM modification has to happen asynchronously. Otherwise this
// is reported as an XHR_SUCCEEDED event.
setTimeout(() => {
document.getElementById('form').style.display = 'none';
}, 50);
}
xhr.send(null);
)");
run_loop.Run();
}
} // namespace autofill