blob: 952b63347ca4c3c57c23bbf730f87fbd635554c6 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <memory>
#include <string>
#include "base/bind.h"
#include "base/callback_list.h"
#include "base/command_line.h"
#include "base/macros.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/metrics/user_action_tester.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/browser/sync/profile_sync_service_factory.h"
#include "chrome/browser/sync/test/integration/profile_sync_service_harness.h"
#include "chrome/browser/sync/test/integration/sync_test.h"
#include "chrome/browser/ui/autofill/chrome_autofill_client.h"
#include "chrome/browser/ui/autofill/popup_constants.h"
#include "chrome/browser/ui/autofill/save_card_bubble_controller_impl.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/location_bar/location_bar.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/views/autofill/dialog_view_ids.h"
#include "chrome/browser/ui/views/autofill/save_card_bubble_views.h"
#include "chrome/browser/ui/views/autofill/save_card_icon_view.h"
#include "chrome/browser/ui/views/location_bar/location_bar_view.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/autofill/content/browser/content_autofill_driver.h"
#include "components/autofill/core/browser/autofill_experiments.h"
#include "components/autofill/core/browser/autofill_test_utils.h"
#include "components/autofill/core/browser/credit_card_save_manager.h"
#include "components/autofill/core/browser/form_data_importer.h"
#include "components/autofill/core/browser/payments/payments_client.h"
#include "components/autofill/core/browser/test_autofill_clock.h"
#include "components/autofill/core/browser/test_event_waiter.h"
#include "components/autofill/core/common/autofill_constants.h"
#include "components/autofill/core/common/autofill_features.h"
#include "components/browser_sync/profile_sync_service.h"
#include "components/keyed_service/content/browser_context_dependency_manager.h"
#include "components/network_session_configurator/common/network_switches.h"
#include "components/signin/core/browser/signin_buildflags.h"
#include "components/sync/test/fake_server/fake_server.h"
#include "components/sync/test/fake_server/fake_server_network_resources.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/test_navigation_observer.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/url_request/test_url_fetcher_factory.h"
#include "services/device/public/cpp/test/scoped_geolocation_overrider.h"
#include "services/identity/public/cpp/identity_manager.h"
#include "services/identity/public/cpp/identity_test_utils.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
#include "services/network/test/test_url_loader_factory.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "ui/events/base_event_utils.h"
#include "ui/views/bubble/bubble_frame_view.h"
#include "ui/views/controls/button/button.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/controls/combobox/combobox.h"
#include "ui/views/controls/styled_label.h"
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/test/widget_test.h"
#include "ui/views/widget/widget.h"
#include "ui/views/window/dialog_client_view.h"
#include "ui/views/window/non_client_view.h"
#if BUILDFLAG(ENABLE_DICE_SUPPORT)
#include "chrome/browser/ui/views/sync/dice_bubble_sync_promo_view.h"
#endif
#if defined(OS_CHROMEOS)
#include "chrome/browser/ui/settings_window_manager_chromeos.h"
#endif
using base::Bucket;
using testing::ElementsAre;
namespace {
const char kCreditCardUploadForm[] =
"/credit_card_upload_form_address_and_cc.html";
const char kCreditCardAndShippingUploadForm[] =
"/credit_card_upload_form_shipping_address.html";
const char kURLGetUploadDetailsRequest[] =
"https://payments.google.com/payments/apis/chromepaymentsservice/"
"getdetailsforsavecard";
const char kResponseGetUploadDetailsSuccess[] =
"{\"legal_message\":{\"line\":[{\"template\":\"Legal message template with "
"link: "
"{0}.\",\"template_parameter\":[{\"display_text\":\"Link\",\"url\":\"https:"
"//www.example.com/\"}]}]},\"context_token\":\"dummy_context_token\"}";
const char kResponsePaymentsFailure[] =
"{\"error\":{\"code\":\"FAILED_PRECONDITION\",\"user_error_message\":\"An "
"unexpected error has occurred. Please try again later.\"}}";
const char kURLUploadCardRequest[] =
"https://payments.google.com/payments/apis/chromepaymentsservice/savecard";
const double kFakeGeolocationLatitude = 1.23;
const double kFakeGeolocationLongitude = 4.56;
} // namespace
namespace autofill {
class SaveCardBubbleViewsFullFormBrowserTest
: public SyncTest,
public CreditCardSaveManager::ObserverForTest,
public SaveCardBubbleControllerImpl::ObserverForTest {
protected:
SaveCardBubbleViewsFullFormBrowserTest() : SyncTest(SINGLE_CLIENT) {}
~SaveCardBubbleViewsFullFormBrowserTest() override = default;
// Various events that can be waited on by the DialogEventWaiter.
enum DialogEvent : int {
OFFERED_LOCAL_SAVE,
REQUESTED_UPLOAD_SAVE,
RECEIVED_GET_UPLOAD_DETAILS_RESPONSE,
SENT_UPLOAD_CARD_REQUEST,
RECEIVED_UPLOAD_CARD_RESPONSE,
STRIKE_CHANGE_COMPLETE,
BUBBLE_SHOWN,
BUBBLE_CLOSED
};
// SyncTest::SetUpOnMainThread:
void SetUpOnMainThread() override {
SyncTest::SetUpOnMainThread();
// Set up the HTTPS server (uses the embedded_test_server).
ASSERT_TRUE(embedded_test_server()->InitializeAndListen());
embedded_test_server()->ServeFilesFromSourceDirectory(
"components/test/data/autofill");
embedded_test_server()->StartAcceptingConnections();
ProfileSyncServiceFactory::GetForProfile(browser()->profile())
->OverrideNetworkResourcesForTest(
std::make_unique<fake_server::FakeServerNetworkResources>(
GetFakeServer()->AsWeakPtr()));
std::string username;
#if defined(OS_CHROMEOS)
// In ChromeOS browser tests, the profile may already by authenticated with
// stub account |user_manager::kStubUserEmail|.
AccountInfo info =
IdentityManagerFactory::GetForProfile(browser()->profile())
->GetPrimaryAccountInfo();
username = info.email;
#endif
if (username.empty())
username = "user@gmail.com";
harness_ = ProfileSyncServiceHarness::Create(
browser()->profile(), username, "password",
ProfileSyncServiceHarness::SigninType::FAKE_SIGNIN);
// Set up the URL loader factory for the payments client so we can intercept
// those network requests too.
test_shared_loader_factory_ =
base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
&test_url_loader_factory_);
ContentAutofillDriver::GetForRenderFrameHost(
GetActiveWebContents()->GetMainFrame())
->autofill_manager()
->client()
->GetPaymentsClient()
->set_url_loader_factory_for_testing(test_shared_loader_factory_);
// Set up this class as the ObserverForTest implementation.
CreditCardSaveManager* credit_card_save_manager =
ContentAutofillDriver::GetForRenderFrameHost(
GetActiveWebContents()->GetMainFrame())
->autofill_manager()
->client()
->GetFormDataImporter()
->credit_card_save_manager_.get();
credit_card_save_manager->SetEventObserverForTesting(this);
// Set up the fake geolocation data.
geolocation_overrider_ =
std::make_unique<device::ScopedGeolocationOverrider>(
kFakeGeolocationLatitude, kFakeGeolocationLongitude);
}
// CreditCardSaveManager::ObserverForTest:
void OnOfferLocalSave() override {
if (event_waiter_)
event_waiter_->OnEvent(DialogEvent::OFFERED_LOCAL_SAVE);
}
// CreditCardSaveManager::ObserverForTest:
void OnDecideToRequestUploadSave() override {
if (event_waiter_)
event_waiter_->OnEvent(DialogEvent::REQUESTED_UPLOAD_SAVE);
}
// CreditCardSaveManager::ObserverForTest:
void OnReceivedGetUploadDetailsResponse() override {
if (event_waiter_)
event_waiter_->OnEvent(DialogEvent::RECEIVED_GET_UPLOAD_DETAILS_RESPONSE);
}
// CreditCardSaveManager::ObserverForTest:
void OnSentUploadCardRequest() override {
if (event_waiter_)
event_waiter_->OnEvent(DialogEvent::SENT_UPLOAD_CARD_REQUEST);
}
// CreditCardSaveManager::ObserverForTest:
void OnReceivedUploadCardResponse() override {
if (event_waiter_)
event_waiter_->OnEvent(DialogEvent::RECEIVED_UPLOAD_CARD_RESPONSE);
}
// CreditCardSaveManager::ObserverForTest:
void OnStrikeChangeComplete() override {
if (event_waiter_)
event_waiter_->OnEvent(DialogEvent::STRIKE_CHANGE_COMPLETE);
}
// SaveCardBubbleControllerImpl::ObserverForTest:
void OnBubbleShown() override {
if (event_waiter_)
event_waiter_->OnEvent(DialogEvent::BUBBLE_SHOWN);
}
// SaveCardBubbleControllerImpl::ObserverForTest:
void OnBubbleClosed() override {
if (event_waiter_)
event_waiter_->OnEvent(DialogEvent::BUBBLE_CLOSED);
}
inline views::Combobox* month_input() {
return static_cast<views::Combobox*>(
FindViewInBubbleById(DialogViewId::EXPIRATION_DATE_DROPBOX_MONTH));
}
inline views::Combobox* year_input() {
return static_cast<views::Combobox*>(
FindViewInBubbleById(DialogViewId::EXPIRATION_DATE_DROPBOX_YEAR));
}
void VerifyExpirationDateDropdownsAreVisible() {
EXPECT_TRUE(FindViewInBubbleById(DialogViewId::MAIN_CONTENT_VIEW_UPLOAD)
->visible());
EXPECT_TRUE(FindViewInBubbleById(DialogViewId::FOOTNOTE_VIEW)->visible());
EXPECT_TRUE(
FindViewInBubbleById(DialogViewId::EXPIRATION_DATE_VIEW)->visible());
EXPECT_TRUE(FindViewInBubbleById(DialogViewId::EXPIRATION_DATE_DROPBOX_YEAR)
->visible());
EXPECT_TRUE(
FindViewInBubbleById(DialogViewId::EXPIRATION_DATE_DROPBOX_MONTH)
->visible());
EXPECT_FALSE(FindViewInBubbleById(DialogViewId::EXPIRATION_DATE_LABEL));
}
void SetUpForEditableExpirationDate() {
// Enable the EditableExpirationDate experiment.
scoped_feature_list_.InitWithFeatures(
// Enabled
{features::kAutofillUpstreamEditableExpirationDate,
features::kAutofillUpstream},
// Disabled
{});
// Start sync.
harness_->SetupSync();
// Set up the Payments RPC.
SetUploadDetailsRpcPaymentsAccepts();
// Submitting the form should still show the upload save bubble and legal
// footer, along with a pair of dropdowns specifically requesting the
// expiration date. (Must wait for response from Payments before accessing
// the controller.)
ResetEventWaiterForSequence(
{DialogEvent::REQUESTED_UPLOAD_SAVE,
DialogEvent::RECEIVED_GET_UPLOAD_DETAILS_RESPONSE});
NavigateTo(kCreditCardUploadForm);
}
void NavigateTo(const std::string& file_path) {
if (file_path.find("data:") == 0U) {
ui_test_utils::NavigateToURL(browser(), GURL(file_path));
} else {
ui_test_utils::NavigateToURL(browser(),
embedded_test_server()->GetURL(file_path));
}
}
void SetAccountFullName(const std::string& full_name) {
identity::IdentityManager* identity_manager =
IdentityManagerFactory::GetForProfile(browser()->profile());
AccountInfo info = identity_manager->GetPrimaryAccountInfo();
info.full_name = full_name;
identity::UpdateAccountInfoForAccount(identity_manager, info);
}
void SubmitForm() {
content::WebContents* web_contents = GetActiveWebContents();
const std::string click_submit_button_js =
"(function() { document.getElementById('submit').click(); })();";
content::TestNavigationObserver nav_observer(web_contents);
ASSERT_TRUE(content::ExecuteScript(web_contents, click_submit_button_js));
nav_observer.Wait();
}
// Should be called for credit_card_upload_form_address_and_cc.html.
void FillAndSubmitForm() {
content::WebContents* web_contents = GetActiveWebContents();
const std::string click_fill_button_js =
"(function() { document.getElementById('fill_form').click(); })();";
ASSERT_TRUE(content::ExecuteScript(web_contents, click_fill_button_js));
SubmitForm();
}
void FillAndSubmitFormWithCardDetailsOnly() {
content::WebContents* web_contents = GetActiveWebContents();
const std::string click_fill_card_button_js =
"(function() { document.getElementById('fill_card_only').click(); "
"})();";
ASSERT_TRUE(
content::ExecuteScript(web_contents, click_fill_card_button_js));
SubmitForm();
}
// Should be called for credit_card_upload_form_address_and_cc.html.
void FillAndSubmitFormWithoutCvc() {
content::WebContents* web_contents = GetActiveWebContents();
const std::string click_fill_button_js =
"(function() { document.getElementById('fill_form').click(); })();";
ASSERT_TRUE(content::ExecuteScript(web_contents, click_fill_button_js));
const std::string click_clear_cvc_button_js =
"(function() { document.getElementById('clear_cvc').click(); })();";
ASSERT_TRUE(
content::ExecuteScript(web_contents, click_clear_cvc_button_js));
SubmitForm();
}
// Should be called for credit_card_upload_form_address_and_cc.html.
void FillAndSubmitFormWithInvalidCvc() {
content::WebContents* web_contents = GetActiveWebContents();
const std::string click_fill_button_js =
"(function() { document.getElementById('fill_form').click(); })();";
ASSERT_TRUE(content::ExecuteScript(web_contents, click_fill_button_js));
const std::string click_fill_invalid_cvc_button_js =
"(function() { document.getElementById('fill_invalid_cvc').click(); "
"})();";
ASSERT_TRUE(
content::ExecuteScript(web_contents, click_fill_invalid_cvc_button_js));
SubmitForm();
}
// Should be called for credit_card_upload_form_address_and_cc.html.
void FillAndSubmitFormWithoutName() {
content::WebContents* web_contents = GetActiveWebContents();
const std::string click_fill_button_js =
"(function() { document.getElementById('fill_form').click(); })();";
ASSERT_TRUE(content::ExecuteScript(web_contents, click_fill_button_js));
const std::string click_clear_name_button_js =
"(function() { document.getElementById('clear_name').click(); })();";
ASSERT_TRUE(
content::ExecuteScript(web_contents, click_clear_name_button_js));
SubmitForm();
}
// Should be called for credit_card_upload_form_shipping_address.html.
void FillAndSubmitFormWithConflictingName() {
content::WebContents* web_contents = GetActiveWebContents();
const std::string click_fill_button_js =
"(function() { document.getElementById('fill_form').click(); })();";
ASSERT_TRUE(content::ExecuteScript(web_contents, click_fill_button_js));
const std::string click_conflicting_name_button_js =
"(function() { document.getElementById('conflicting_name').click(); "
"})();";
ASSERT_TRUE(
content::ExecuteScript(web_contents, click_conflicting_name_button_js));
SubmitForm();
}
// Should be called for credit_card_upload_form_address_and_cc.html.
void FillAndSubmitFormWithoutExpirationDate() {
content::WebContents* web_contents = GetActiveWebContents();
const std::string click_fill_button_js =
"(function() { document.getElementById('fill_form').click(); })();";
ASSERT_TRUE(content::ExecuteScript(web_contents, click_fill_button_js));
const std::string click_clear_expiration_date_button_js =
"(function() { "
"document.getElementById('clear_expiration_date').click(); "
"})();";
ASSERT_TRUE(content::ExecuteScript(web_contents,
click_clear_expiration_date_button_js));
SubmitForm();
}
// Should be called for credit_card_upload_form_address_and_cc.html.
void FillAndSubmitFormWithExpirationMonthOnly(const std::string& month) {
content::WebContents* web_contents = GetActiveWebContents();
const std::string click_fill_button_js =
"(function() { document.getElementById('fill_form').click(); })();";
ASSERT_TRUE(content::ExecuteScript(web_contents, click_fill_button_js));
const std::string click_clear_expiration_date_button_js =
"(function() { "
"document.getElementById('clear_expiration_date').click(); "
"})();";
ASSERT_TRUE(content::ExecuteScript(web_contents,
click_clear_expiration_date_button_js));
std::string set_month_js =
"(function() { document.getElementById('cc_month_exp_id').value =" +
month + ";})();";
ASSERT_TRUE(content::ExecuteScript(web_contents, set_month_js));
SubmitForm();
}
// Should be called for credit_card_upload_form_address_and_cc.html.
void FillAndSubmitFormWithExpirationYearOnly(const std::string& year) {
content::WebContents* web_contents = GetActiveWebContents();
const std::string click_fill_button_js =
"(function() { document.getElementById('fill_form').click(); })();";
ASSERT_TRUE(content::ExecuteScript(web_contents, click_fill_button_js));
const std::string click_clear_expiration_date_button_js =
"(function() { "
"document.getElementById('clear_expiration_date').click(); "
"})();";
ASSERT_TRUE(content::ExecuteScript(web_contents,
click_clear_expiration_date_button_js));
std::string set_year_js =
"(function() { document.getElementById('cc_year_exp_id').value =" +
year + ";})();";
ASSERT_TRUE(content::ExecuteScript(web_contents, set_year_js));
SubmitForm();
}
// Should be called for credit_card_upload_form_address_and_cc.html.
void FillAndSubmitFormWithSpecificExpirationDate(const std::string& month,
const std::string& year) {
content::WebContents* web_contents = GetActiveWebContents();
const std::string click_fill_button_js =
"(function() { document.getElementById('fill_form').click(); })();";
ASSERT_TRUE(content::ExecuteScript(web_contents, click_fill_button_js));
std::string set_month_js =
"(function() { document.getElementById('cc_month_exp_id').value =" +
month + ";})();";
ASSERT_TRUE(content::ExecuteScript(web_contents, set_month_js));
std::string set_year_js =
"(function() { document.getElementById('cc_year_exp_id').value =" +
year + ";})();";
ASSERT_TRUE(content::ExecuteScript(web_contents, set_year_js));
SubmitForm();
}
// Should be called for credit_card_upload_form_address_and_cc.html.
void FillAndSubmitFormWithoutAddress() {
content::WebContents* web_contents = GetActiveWebContents();
const std::string click_fill_button_js =
"(function() { document.getElementById('fill_form').click(); })();";
ASSERT_TRUE(content::ExecuteScript(web_contents, click_fill_button_js));
const std::string click_clear_address_button_js =
"(function() { document.getElementById('clear_address').click(); })();";
ASSERT_TRUE(
content::ExecuteScript(web_contents, click_clear_address_button_js));
SubmitForm();
}
// Should be called for credit_card_upload_form_shipping_address.html.
void FillAndSubmitFormWithConflictingPostalCode() {
content::WebContents* web_contents = GetActiveWebContents();
const std::string click_fill_button_js =
"(function() { document.getElementById('fill_form').click(); })();";
ASSERT_TRUE(content::ExecuteScript(web_contents, click_fill_button_js));
const std::string click_conflicting_postal_code_button_js =
"(function() { "
"document.getElementById('conflicting_postal_code').click(); })();";
ASSERT_TRUE(content::ExecuteScript(
web_contents, click_conflicting_postal_code_button_js));
SubmitForm();
}
void SetUploadDetailsRpcPaymentsAccepts() {
test_url_loader_factory()->AddResponse(kURLGetUploadDetailsRequest,
kResponseGetUploadDetailsSuccess);
}
void SetUploadDetailsRpcPaymentsDeclines() {
test_url_loader_factory()->AddResponse(kURLGetUploadDetailsRequest,
kResponsePaymentsFailure);
}
void SetUploadDetailsRpcServerError() {
test_url_loader_factory()->AddResponse(kURLGetUploadDetailsRequest,
kResponseGetUploadDetailsSuccess,
net::HTTP_INTERNAL_SERVER_ERROR);
}
void SetUploadCardRpcPaymentsFails() {
test_url_loader_factory()->AddResponse(kURLUploadCardRequest,
kResponsePaymentsFailure);
}
void ClickOnView(views::View* view) {
DCHECK(view);
ui::MouseEvent pressed(ui::ET_MOUSE_PRESSED, gfx::Point(), gfx::Point(),
ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON,
ui::EF_LEFT_MOUSE_BUTTON);
view->OnMousePressed(pressed);
ui::MouseEvent released_event =
ui::MouseEvent(ui::ET_MOUSE_RELEASED, gfx::Point(), gfx::Point(),
ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON,
ui::EF_LEFT_MOUSE_BUTTON);
view->OnMouseReleased(released_event);
}
void ClickOnDialogView(views::View* view) {
GetSaveCardBubbleViews()
->GetDialogClientView()
->ResetViewShownTimeStampForTesting();
views::BubbleFrameView* bubble_frame_view =
static_cast<views::BubbleFrameView*>(GetSaveCardBubbleViews()
->GetWidget()
->non_client_view()
->frame_view());
bubble_frame_view->ResetViewShownTimeStampForTesting();
ClickOnView(view);
}
void ClickOnDialogViewAndWait(views::View* view) {
EXPECT_TRUE(GetSaveCardBubbleViews());
views::test::WidgetDestroyedWaiter destroyed_waiter(
GetSaveCardBubbleViews()->GetWidget());
ClickOnDialogView(view);
destroyed_waiter.Wait();
EXPECT_FALSE(GetSaveCardBubbleViews());
}
void ClickOnDialogViewWithId(DialogViewId view_id) {
ClickOnDialogView(FindViewInBubbleById(view_id));
}
void ClickOnDialogViewWithIdAndWait(DialogViewId view_id) {
ClickOnDialogViewAndWait(FindViewInBubbleById(view_id));
}
views::View* FindViewInBubbleById(DialogViewId view_id) {
SaveCardBubbleViews* save_card_bubble_views = GetSaveCardBubbleViews();
DCHECK(save_card_bubble_views);
views::View* specified_view =
save_card_bubble_views->GetViewByID(static_cast<int>(view_id));
if (!specified_view) {
// Many of the save card bubble's inner Views are not child views but
// rather contained by its DialogClientView. If we didn't find what we
// were looking for, check there as well.
specified_view =
save_card_bubble_views->GetDialogClientView()->GetViewByID(
static_cast<int>(view_id));
}
if (!specified_view) {
// Additionally, the save card bubble's footnote view is not part of its
// main bubble, and contains elements such as the legal message links.
// If we didn't find what we were looking for, check there as well.
views::View* footnote_view =
save_card_bubble_views->GetFootnoteViewForTesting();
if (footnote_view) {
specified_view = footnote_view->GetViewByID(static_cast<int>(view_id));
}
}
return specified_view;
}
void ClickOnCancelButton(bool strike_expected = false) {
SaveCardBubbleViews* save_card_bubble_views = GetSaveCardBubbleViews();
DCHECK(save_card_bubble_views);
if (strike_expected &&
(base::FeatureList::IsEnabled(
features::kAutofillSaveCreditCardUsesStrikeSystem) ||
base::FeatureList::IsEnabled(
features::kAutofillSaveCreditCardUsesStrikeSystemV2))) {
ResetEventWaiterForSequence(
{DialogEvent::STRIKE_CHANGE_COMPLETE, DialogEvent::BUBBLE_CLOSED});
} else {
ResetEventWaiterForSequence({DialogEvent::BUBBLE_CLOSED});
}
ClickOnDialogViewWithIdAndWait(DialogViewId::CANCEL_BUTTON);
DCHECK(!GetSaveCardBubbleViews());
}
void ClickOnCloseButton() {
SaveCardBubbleViews* save_card_bubble_views = GetSaveCardBubbleViews();
DCHECK(save_card_bubble_views);
ResetEventWaiterForSequence({DialogEvent::BUBBLE_CLOSED});
ClickOnDialogViewAndWait(
save_card_bubble_views->GetBubbleFrameView()->GetCloseButtonForTest());
DCHECK(!GetSaveCardBubbleViews());
}
SaveCardBubbleViews* GetSaveCardBubbleViews() {
SaveCardBubbleControllerImpl* save_card_bubble_controller_impl =
SaveCardBubbleControllerImpl::FromWebContents(GetActiveWebContents());
if (!save_card_bubble_controller_impl)
return nullptr;
SaveCardBubbleView* save_card_bubble_view =
save_card_bubble_controller_impl->save_card_bubble_view();
if (!save_card_bubble_view)
return nullptr;
return static_cast<SaveCardBubbleViews*>(save_card_bubble_view);
}
SaveCardIconView* GetSaveCardIconView() {
if (!browser())
return nullptr;
LocationBarView* location_bar_view =
static_cast<LocationBarView*>(browser()->window()->GetLocationBar());
DCHECK(location_bar_view->save_credit_card_icon_view());
return location_bar_view->save_credit_card_icon_view();
}
content::WebContents* GetActiveWebContents() {
return browser()->tab_strip_model()->GetActiveWebContents();
}
void AddEventObserverToController() {
SaveCardBubbleControllerImpl* save_card_bubble_controller_impl =
SaveCardBubbleControllerImpl::FromWebContents(GetActiveWebContents());
DCHECK(save_card_bubble_controller_impl);
save_card_bubble_controller_impl->SetEventObserverForTesting(this);
}
void ReduceAnimationTime() {
GetSaveCardIconView()->ReduceAnimationTimeForTesting();
}
void ResetEventWaiterForSequence(std::list<DialogEvent> event_sequence) {
event_waiter_ =
std::make_unique<EventWaiter<DialogEvent>>(std::move(event_sequence));
}
void WaitForObservedEvent() { event_waiter_->Wait(); }
network::TestURLLoaderFactory* test_url_loader_factory() {
return &test_url_loader_factory_;
}
std::unique_ptr<
base::CallbackList<void(content::BrowserContext*)>::Subscription>
will_create_browser_context_services_subscription_;
base::test::ScopedFeatureList scoped_feature_list_;
std::unique_ptr<ProfileSyncServiceHarness> harness_;
private:
std::unique_ptr<autofill::EventWaiter<DialogEvent>> event_waiter_;
std::unique_ptr<net::FakeURLFetcherFactory> url_fetcher_factory_;
network::TestURLLoaderFactory test_url_loader_factory_;
scoped_refptr<network::SharedURLLoaderFactory> test_shared_loader_factory_;
std::unique_ptr<device::ScopedGeolocationOverrider> geolocation_overrider_;
DISALLOW_COPY_AND_ASSIGN(SaveCardBubbleViewsFullFormBrowserTest);
};
// Tests the local save bubble. Ensures that clicking the [Save] button
// successfully causes the bubble to go away.
IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
Local_ClickingSaveClosesBubble) {
// Disable the sign-in promo.
scoped_feature_list_.InitAndDisableFeature(
features::kAutofillSaveCardSignInAfterLocalSave);
// Submitting the form without signed in user should show the local save
// bubble.
ResetEventWaiterForSequence({DialogEvent::OFFERED_LOCAL_SAVE});
NavigateTo(kCreditCardUploadForm);
FillAndSubmitForm();
WaitForObservedEvent();
EXPECT_TRUE(
FindViewInBubbleById(DialogViewId::MAIN_CONTENT_VIEW_LOCAL)->visible());
// Clicking [Save] should accept and close it.
base::HistogramTester histogram_tester;
base::UserActionTester user_action_tester;
ClickOnDialogViewWithIdAndWait(DialogViewId::OK_BUTTON);
// UMA should have recorded bubble acceptance.
histogram_tester.ExpectUniqueSample(
"Autofill.SaveCreditCardPrompt.Local.FirstShow",
AutofillMetrics::SAVE_CARD_PROMPT_END_ACCEPTED, 1);
// No bubble should be showing and no sign-in impression should have been
// recorded.
EXPECT_EQ(nullptr, GetSaveCardBubbleViews());
EXPECT_FALSE(GetSaveCardIconView()->visible());
EXPECT_EQ(0, user_action_tester.GetActionCount(
"Signin_Impression_FromSaveCardBubble"));
}
// Tests the local save bubble. Ensures that clicking the [No thanks] button
// successfully causes the bubble to go away.
IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
Local_ClickingNoThanksClosesBubble) {
// Enable the updated UI.
scoped_feature_list_.InitWithFeatures(
// Enabled
{features::kAutofillSaveCardImprovedUserConsent},
// Disabled
{features::kAutofillSaveCreditCardUsesStrikeSystem,
features::kAutofillSaveCreditCardUsesStrikeSystemV2});
// Submitting the form and having Payments decline offering to save should
// show the local save bubble.
// (Must wait for response from Payments before accessing the controller.)
ResetEventWaiterForSequence({DialogEvent::OFFERED_LOCAL_SAVE});
NavigateTo(kCreditCardUploadForm);
FillAndSubmitForm();
WaitForObservedEvent();
EXPECT_TRUE(
FindViewInBubbleById(DialogViewId::MAIN_CONTENT_VIEW_LOCAL)->visible());
// Clicking [No thanks] should cancel and close it.
base::HistogramTester histogram_tester;
ClickOnCancelButton();
// UMA should have recorded bubble rejection.
histogram_tester.ExpectUniqueSample(
"Autofill.SaveCreditCardPrompt.Local.FirstShow",
AutofillMetrics::SAVE_CARD_PROMPT_END_DENIED, 1);
}
// Tests the sign in promo bubble. Ensures that clicking the [Save] button
// on the local save bubble successfully causes the sign in promo to show.
#if !defined(OS_CHROMEOS)
IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
Local_ClickingSaveShowsSigninPromo) {
// Enable the sign-in promo.
scoped_feature_list_.InitAndEnableFeature(
features::kAutofillSaveCardSignInAfterLocalSave);
// Submitting the form without signed in user should show the local save
// bubble.
ResetEventWaiterForSequence({DialogEvent::OFFERED_LOCAL_SAVE});
NavigateTo(kCreditCardUploadForm);
FillAndSubmitForm();
WaitForObservedEvent();
EXPECT_TRUE(
FindViewInBubbleById(DialogViewId::MAIN_CONTENT_VIEW_LOCAL)->visible());
// Adding an event observer to the controller so we can wait for the bubble to
// show.
AddEventObserverToController();
ReduceAnimationTime();
ResetEventWaiterForSequence(
{DialogEvent::BUBBLE_CLOSED, DialogEvent::BUBBLE_SHOWN});
// Click [Save] should close the offer-to-save bubble
// and pop up the sign-in promo.
base::UserActionTester user_action_tester;
ClickOnDialogViewWithId(DialogViewId::OK_BUTTON);
WaitForObservedEvent();
// Sign-in promo should be showing and user actions should have recorded
// impression.
EXPECT_TRUE(
FindViewInBubbleById(DialogViewId::SIGN_IN_PROMO_VIEW)->visible());
}
#endif
// Tests the sign in promo bubble. Ensures that the sign-in promo
// is not shown when the user is signed-in and syncing, even if the local save
// bubble is shown.
IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
Local_NoSigninPromoShowsWhenUserIsSyncing) {
// Enable the sign-in promo.
scoped_feature_list_.InitWithFeatures(
// Enabled
{features::kAutofillUpstream,
features::kAutofillSaveCardSignInAfterLocalSave},
// Disabled
{});
// Start sync.
harness_->SetupSync();
// Set up the Payments RPC.
SetUploadDetailsRpcPaymentsDeclines();
// Submitting the form and having Payments decline offering to save should
// show the local save bubble.
// (Must wait for response from Payments before accessing the controller.)
ResetEventWaiterForSequence(
{DialogEvent::REQUESTED_UPLOAD_SAVE,
DialogEvent::RECEIVED_GET_UPLOAD_DETAILS_RESPONSE,
DialogEvent::OFFERED_LOCAL_SAVE});
NavigateTo(kCreditCardUploadForm);
FillAndSubmitForm();
WaitForObservedEvent();
EXPECT_TRUE(
FindViewInBubbleById(DialogViewId::MAIN_CONTENT_VIEW_LOCAL)->visible());
// Click [Save] should close the offer-to-save bubble
// but no sign-in promo should show because user is signed in.
base::UserActionTester user_action_tester;
ClickOnDialogViewWithIdAndWait(DialogViewId::OK_BUTTON);
// No bubble should be showing and no sign-in impression should have been
// recorded.
EXPECT_EQ(nullptr, GetSaveCardBubbleViews());
EXPECT_EQ(0, user_action_tester.GetActionCount(
"Signin_Impression_FromSaveCardBubble"));
}
// Tests the sign in promo bubble. Ensures that signin impression is recorded
// when promo is shown.
#if !defined(OS_CHROMEOS)
IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
Local_Metrics_SigninImpressionSigninPromo) {
// Enable the sign-in promo.
scoped_feature_list_.InitAndEnableFeature(
features::kAutofillSaveCardSignInAfterLocalSave);
// Submitting the form without signed in user should show the local save
// bubble.
ResetEventWaiterForSequence({DialogEvent::OFFERED_LOCAL_SAVE});
NavigateTo(kCreditCardUploadForm);
FillAndSubmitForm();
WaitForObservedEvent();
// Adding an event observer to the controller so we can wait for the bubble to
// show.
AddEventObserverToController();
ReduceAnimationTime();
ResetEventWaiterForSequence(
{DialogEvent::BUBBLE_CLOSED, DialogEvent::BUBBLE_SHOWN});
// Click [Save] should close the offer-to-save bubble
// and pop up the sign-in promo.
base::UserActionTester user_action_tester;
ClickOnDialogViewWithId(DialogViewId::OK_BUTTON);
WaitForObservedEvent();
// User actions should have recorded impression.
EXPECT_EQ(1, user_action_tester.GetActionCount(
"Signin_Impression_FromSaveCardBubble"));
}
#endif
// Tests the sign in promo bubble. Ensures that signin action is recorded when
// user accepts promo.
#if BUILDFLAG(ENABLE_DICE_SUPPORT)
IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
Local_Metrics_AcceptingSigninPromo) {
// Enable the sign-in promo.
scoped_feature_list_.InitAndEnableFeature(
features::kAutofillSaveCardSignInAfterLocalSave);
// Submitting the form without signed in user should show the local save
// bubble.
ResetEventWaiterForSequence({DialogEvent::OFFERED_LOCAL_SAVE});
NavigateTo(kCreditCardUploadForm);
FillAndSubmitForm();
WaitForObservedEvent();
// Adding an event observer to the controller so we can wait for the bubble to
// show.
AddEventObserverToController();
ReduceAnimationTime();
ResetEventWaiterForSequence(
{DialogEvent::BUBBLE_CLOSED, DialogEvent::BUBBLE_SHOWN});
// Click [Save] should close the offer-to-save bubble
// and pop up the sign-in promo.
base::UserActionTester user_action_tester;
ClickOnDialogViewWithId(DialogViewId::OK_BUTTON);
WaitForObservedEvent();
// Click on [Sign in] button.
ClickOnDialogView(static_cast<DiceBubbleSyncPromoView*>(
FindViewInBubbleById(DialogViewId::SIGN_IN_VIEW))
->GetSigninButtonForTesting());
// User actions should have recorded impression and click.
EXPECT_EQ(1, user_action_tester.GetActionCount(
"Signin_Impression_FromSaveCardBubble"));
EXPECT_EQ(
1, user_action_tester.GetActionCount("Signin_Signin_FromSaveCardBubble"));
}
#endif
// Tests the manage cards bubble. Ensures that it shows up by clicking the
// credit card icon.
IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
Local_ClickingIconShowsManageCards) {
// Enable the sign-in promo.
scoped_feature_list_.InitAndEnableFeature(
features::kAutofillSaveCardSignInAfterLocalSave);
// Submitting the form without signed in user should show the local save
// bubble.
ResetEventWaiterForSequence({DialogEvent::OFFERED_LOCAL_SAVE});
NavigateTo(kCreditCardUploadForm);
FillAndSubmitForm();
WaitForObservedEvent();
// Adding an event observer to the controller so we can wait for the bubble to
// show.
AddEventObserverToController();
ReduceAnimationTime();
#if !defined(OS_CHROMEOS)
ResetEventWaiterForSequence(
{DialogEvent::BUBBLE_CLOSED, DialogEvent::BUBBLE_SHOWN});
#endif
// Click [Save] should close the offer-to-save bubble and show "Card saved"
// animation -- followed by the sign-in promo (if not on Chrome OS).
ClickOnDialogViewWithIdAndWait(DialogViewId::OK_BUTTON);
#if !defined(OS_CHROMEOS)
// Wait for and then close the promo.
WaitForObservedEvent();
ClickOnCloseButton();
#endif
// Open up Manage Cards prompt.
base::HistogramTester histogram_tester;
ResetEventWaiterForSequence({DialogEvent::BUBBLE_SHOWN});
ClickOnView(GetSaveCardIconView());
WaitForObservedEvent();
// Bubble should be showing.
EXPECT_TRUE(FindViewInBubbleById(DialogViewId::MANAGE_CARDS_VIEW)->visible());
histogram_tester.ExpectUniqueSample("Autofill.ManageCardsPrompt.Local",
AutofillMetrics::MANAGE_CARDS_SHOWN, 1);
}
// Tests the manage cards bubble. Ensures that clicking the [Manage cards]
// button redirects properly.
IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
Local_ManageCardsButtonRedirects) {
// Enable the sign-in promo.
scoped_feature_list_.InitAndEnableFeature(
features::kAutofillSaveCardSignInAfterLocalSave);
// Submitting the form without signed in user should show the local save
// bubble.
ResetEventWaiterForSequence({DialogEvent::OFFERED_LOCAL_SAVE});
NavigateTo(kCreditCardUploadForm);
FillAndSubmitForm();
WaitForObservedEvent();
// Adding an event observer to the controller so we can wait for the bubble to
// show.
AddEventObserverToController();
ReduceAnimationTime();
#if !defined(OS_CHROMEOS)
ResetEventWaiterForSequence(
{DialogEvent::BUBBLE_CLOSED, DialogEvent::BUBBLE_SHOWN});
#endif
// Click [Save] should close the offer-to-save bubble and show "Card saved"
// animation -- followed by the sign-in promo (if not on Chrome OS).
ClickOnDialogViewWithIdAndWait(DialogViewId::OK_BUTTON);
#if !defined(OS_CHROMEOS)
// Wait for and then close the promo.
WaitForObservedEvent();
ClickOnCloseButton();
#endif
// Open up Manage Cards prompt.
base::HistogramTester histogram_tester;
ResetEventWaiterForSequence({DialogEvent::BUBBLE_SHOWN});
ClickOnView(GetSaveCardIconView());
WaitForObservedEvent();
// Click on the redirect button.
ClickOnDialogViewWithId(DialogViewId::MANAGE_CARDS_BUTTON);
#if defined(OS_CHROMEOS)
// ChromeOS should have opened up the settings window.
EXPECT_TRUE(
chrome::SettingsWindowManager::GetInstance()->FindBrowserForProfile(
browser()->profile()));
#else
// Otherwise, another tab should have opened.
EXPECT_EQ(2, browser()->tab_strip_model()->count());
#endif
// Metrics should have been recorded correctly.
EXPECT_THAT(
histogram_tester.GetAllSamples("Autofill.ManageCardsPrompt.Local"),
ElementsAre(Bucket(AutofillMetrics::MANAGE_CARDS_SHOWN, 1),
Bucket(AutofillMetrics::MANAGE_CARDS_MANAGE_CARDS, 1)));
}
// Tests the manage cards bubble. Ensures that clicking the [Done]
// button closes the bubble.
IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
Local_ManageCardsDoneButtonClosesBubble) {
// Enable the sign-in promo.
scoped_feature_list_.InitAndEnableFeature(
features::kAutofillSaveCardSignInAfterLocalSave);
// Submitting the form without signed in user should show the local save
// bubble.
ResetEventWaiterForSequence({DialogEvent::OFFERED_LOCAL_SAVE});
NavigateTo(kCreditCardUploadForm);
FillAndSubmitForm();
WaitForObservedEvent();
// Adding an event observer to the controller so we can wait for the bubble to
// show.
AddEventObserverToController();
ReduceAnimationTime();
#if !defined(OS_CHROMEOS)
ResetEventWaiterForSequence(
{DialogEvent::BUBBLE_CLOSED, DialogEvent::BUBBLE_SHOWN});
#endif
// Click [Save] should close the offer-to-save bubble and show "Card saved"
// animation -- followed by the sign-in promo (if not on Chrome OS).
ClickOnDialogViewWithIdAndWait(DialogViewId::OK_BUTTON);
#if !defined(OS_CHROMEOS)
// Wait for and then close the promo.
WaitForObservedEvent();
ClickOnCloseButton();
#endif
// Open up Manage Cards prompt.
base::HistogramTester histogram_tester;
ResetEventWaiterForSequence({DialogEvent::BUBBLE_SHOWN});
ClickOnView(GetSaveCardIconView());
WaitForObservedEvent();
// Click on the [Done] button.
ClickOnDialogViewWithIdAndWait(DialogViewId::OK_BUTTON);
// No bubble should be showing now and metrics should be recorded correctly.
EXPECT_EQ(nullptr, GetSaveCardBubbleViews());
EXPECT_THAT(
histogram_tester.GetAllSamples("Autofill.ManageCardsPrompt.Local"),
ElementsAre(Bucket(AutofillMetrics::MANAGE_CARDS_SHOWN, 1),
Bucket(AutofillMetrics::MANAGE_CARDS_DONE, 1)));
}
// Tests the manage cards bubble. Ensures that sign-in impression is recorded
// correctly.
#if !defined(OS_CHROMEOS)
IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
Local_Metrics_SigninImpressionManageCards) {
// Enable the sign-in promo.
scoped_feature_list_.InitAndEnableFeature(
features::kAutofillSaveCardSignInAfterLocalSave);
// Submitting the form without signed in user should show the local save
// bubble.
ResetEventWaiterForSequence({DialogEvent::OFFERED_LOCAL_SAVE});
NavigateTo(kCreditCardUploadForm);
FillAndSubmitForm();
WaitForObservedEvent();
// Adding an event observer to the controller so we can wait for the bubble to
// show.
AddEventObserverToController();
ReduceAnimationTime();
ResetEventWaiterForSequence(
{DialogEvent::BUBBLE_CLOSED, DialogEvent::BUBBLE_SHOWN});
// Click [Save] should close the offer-to-save bubble
// and pop up the sign-in promo.
base::UserActionTester user_action_tester;
ClickOnDialogViewWithId(DialogViewId::OK_BUTTON);
WaitForObservedEvent();
// Close promo.
ClickOnCloseButton();
// Open up Manage Cards prompt.
ResetEventWaiterForSequence({DialogEvent::BUBBLE_SHOWN});
ClickOnView(GetSaveCardIconView());
WaitForObservedEvent();
// User actions should have recorded impression.
EXPECT_EQ(1, user_action_tester.GetActionCount(
"Signin_Impression_FromManageCardsBubble"));
}
#endif
// Tests the Manage Cards bubble. Ensures that signin action is recorded when
// user accepts footnote promo.
#if BUILDFLAG(ENABLE_DICE_SUPPORT)
IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
Local_Metrics_AcceptingFootnotePromoManageCards) {
// Enable the sign-in promo.
scoped_feature_list_.InitAndEnableFeature(
features::kAutofillSaveCardSignInAfterLocalSave);
// Submitting the form without signed in user should show the local save
// bubble.
ResetEventWaiterForSequence({DialogEvent::OFFERED_LOCAL_SAVE});
NavigateTo(kCreditCardUploadForm);
FillAndSubmitForm();
WaitForObservedEvent();
// Adding an event observer to the controller so we can wait for the bubble to
// show.
AddEventObserverToController();
ReduceAnimationTime();
ResetEventWaiterForSequence(
{DialogEvent::BUBBLE_CLOSED, DialogEvent::BUBBLE_SHOWN});
// Click [Save] should close the offer-to-save bubble
// and pop up the sign-in promo.
base::UserActionTester user_action_tester;
ClickOnDialogViewWithId(DialogViewId::OK_BUTTON);
WaitForObservedEvent();
// Close promo.
ClickOnCloseButton();
// Open up Manage Cards prompt.
ResetEventWaiterForSequence({DialogEvent::BUBBLE_SHOWN});
ClickOnView(GetSaveCardIconView());
WaitForObservedEvent();
// Click on [Sign in] button in footnote.
ClickOnDialogView(static_cast<DiceBubbleSyncPromoView*>(
FindViewInBubbleById(DialogViewId::FOOTNOTE_VIEW))
->GetSigninButtonForTesting());
// User actions should have recorded impression and click.
EXPECT_EQ(1, user_action_tester.GetActionCount(
"Signin_Impression_FromManageCardsBubble"));
EXPECT_EQ(1, user_action_tester.GetActionCount(
"Signin_Signin_FromManageCardsBubble"));
}
#endif
// Tests the local save bubble. Ensures that the bubble does not have a
// [No thanks] button (it has an [X] Close button instead.)
IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
Local_ShouldNotHaveNoThanksButton) {
// Disable the updated UI.
scoped_feature_list_.InitAndDisableFeature(
features::kAutofillSaveCardImprovedUserConsent);
// Submitting the form without signed in user should show the local save
// bubble.
ResetEventWaiterForSequence({DialogEvent::OFFERED_LOCAL_SAVE});
NavigateTo(kCreditCardUploadForm);
FillAndSubmitForm();
WaitForObservedEvent();
EXPECT_TRUE(
FindViewInBubbleById(DialogViewId::MAIN_CONTENT_VIEW_LOCAL)->visible());
// Assert that the cancel button cannot be found.
EXPECT_FALSE(FindViewInBubbleById(DialogViewId::CANCEL_BUTTON));
}
// Tests the local save bubble. Ensures that the bubble behaves correctly if
// dismissed and then immediately torn down (e.g. by closing browser window)
// before the asynchronous close completes. Regression test for
// https://crbug.com/842577 .
IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
Local_SynchronousCloseAfterAsynchronousClose) {
// Submitting the form without signed in user should show the local save
// bubble.
ResetEventWaiterForSequence({DialogEvent::OFFERED_LOCAL_SAVE});
NavigateTo(kCreditCardUploadForm);
FillAndSubmitForm();
WaitForObservedEvent();
SaveCardBubbleViews* bubble = GetSaveCardBubbleViews();
EXPECT_TRUE(bubble);
views::Widget* bubble_widget = bubble->GetWidget();
EXPECT_TRUE(bubble_widget);
EXPECT_TRUE(bubble_widget->IsVisible());
bubble->Hide();
EXPECT_FALSE(bubble_widget->IsVisible());
// The bubble is immediately hidden, but it can still receive events here.
// Simulate an OS event arriving to destroy the Widget.
bubble_widget->CloseNow();
// |bubble| and |bubble_widget| now point to deleted objects.
// Simulate closing the browser window.
browser()->tab_strip_model()->CloseAllTabs();
// Process the asynchronous close (which should do nothing).
base::RunLoop().RunUntilIdle();
}
// Tests the upload save bubble. Ensures that clicking the [Save] button
// successfully causes the bubble to go away and sends an UploadCardRequest RPC
// to Google Payments.
IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
Upload_ClickingSaveClosesBubble) {
scoped_feature_list_.InitAndEnableFeature(features::kAutofillUpstream);
// Start sync.
harness_->SetupSync();
// Set up the Payments RPC.
SetUploadDetailsRpcPaymentsAccepts();
// Submitting the form should show the upload save bubble and legal footer.
// (Must wait for response from Payments before accessing the controller.)
ResetEventWaiterForSequence(
{DialogEvent::REQUESTED_UPLOAD_SAVE,
DialogEvent::RECEIVED_GET_UPLOAD_DETAILS_RESPONSE});
NavigateTo(kCreditCardUploadForm);
FillAndSubmitForm();
WaitForObservedEvent();
EXPECT_TRUE(
FindViewInBubbleById(DialogViewId::MAIN_CONTENT_VIEW_UPLOAD)->visible());
EXPECT_TRUE(FindViewInBubbleById(DialogViewId::FOOTNOTE_VIEW)->visible());
// Clicking [Save] should accept and close it, then send an UploadCardRequest
// to Google Payments.
ResetEventWaiterForSequence({DialogEvent::SENT_UPLOAD_CARD_REQUEST});
base::HistogramTester histogram_tester;
ClickOnDialogViewWithIdAndWait(DialogViewId::OK_BUTTON);
// UMA should have recorded bubble acceptance.
histogram_tester.ExpectUniqueSample(
"Autofill.SaveCreditCardPrompt.Upload.FirstShow",
AutofillMetrics::SAVE_CARD_PROMPT_END_ACCEPTED, 1);
}
// Tests the upload save bubble. Ensures that clicking the [No thanks] button
// successfully causes the bubble to go away.
IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
Upload_ClickingNoThanksClosesBubble) {
// Enable the updated UI.
scoped_feature_list_.InitWithFeatures(
// Enabled
{features::kAutofillSaveCardImprovedUserConsent,
features::kAutofillUpstream},
// Disabled
{features::kAutofillSaveCreditCardUsesStrikeSystem,
features::kAutofillSaveCreditCardUsesStrikeSystemV2});
// Start sync.
harness_->SetupSync();
// Set up the Payments RPC.
SetUploadDetailsRpcPaymentsAccepts();
// Submitting the form should show the upload save bubble and legal footer.
// (Must wait for response from Payments before accessing the controller.)
ResetEventWaiterForSequence(
{DialogEvent::REQUESTED_UPLOAD_SAVE,
DialogEvent::RECEIVED_GET_UPLOAD_DETAILS_RESPONSE});
NavigateTo(kCreditCardUploadForm);
FillAndSubmitForm();
WaitForObservedEvent();
EXPECT_TRUE(
FindViewInBubbleById(DialogViewId::MAIN_CONTENT_VIEW_UPLOAD)->visible());
EXPECT_TRUE(FindViewInBubbleById(DialogViewId::FOOTNOTE_VIEW)->visible());
// Clicking [No thanks] should cancel and close it.
base::HistogramTester histogram_tester;
ClickOnCancelButton();
// UMA should have recorded bubble rejection.
histogram_tester.ExpectUniqueSample(
"Autofill.SaveCreditCardPrompt.Upload.FirstShow",
AutofillMetrics::SAVE_CARD_PROMPT_END_DENIED, 1);
}
// Tests the upload save bubble. Ensures that the bubble does not have a
// [No thanks] button (it has an [X] Close button instead.)
IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
Upload_ShouldNotHaveNoThanksButton) {
// Disable the updated UI.
scoped_feature_list_.InitWithFeatures(
// Enabled
{features::kAutofillUpstream},
// Disable the updated UI.
{features::kAutofillSaveCardImprovedUserConsent});
// Start sync.
harness_->SetupSync();
// Set up the Payments RPC.
SetUploadDetailsRpcPaymentsAccepts();
// Submitting the form should show the upload save bubble and legal footer.
// (Must wait for response from Payments before accessing the controller.)
ResetEventWaiterForSequence(
{DialogEvent::REQUESTED_UPLOAD_SAVE,
DialogEvent::RECEIVED_GET_UPLOAD_DETAILS_RESPONSE});
NavigateTo(kCreditCardUploadForm);
FillAndSubmitForm();
WaitForObservedEvent();
EXPECT_TRUE(
FindViewInBubbleById(DialogViewId::MAIN_CONTENT_VIEW_UPLOAD)->visible());
EXPECT_TRUE(FindViewInBubbleById(DialogViewId::FOOTNOTE_VIEW)->visible());
// Assert that the cancel button cannot be found.
EXPECT_FALSE(FindViewInBubbleById(DialogViewId::CANCEL_BUTTON));
}
// Tests the upload save bubble. Ensures that clicking the top-right [X] close
// button successfully causes the bubble to go away.
IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
Upload_ClickingCloseClosesBubble) {
scoped_feature_list_.InitAndEnableFeature(features::kAutofillUpstream);
// Start sync.
harness_->SetupSync();
// Set up the Payments RPC.
SetUploadDetailsRpcPaymentsAccepts();
// Submitting the form should show the upload save bubble and legal footer.
// (Must wait for response from Payments before accessing the controller.)
ResetEventWaiterForSequence(
{DialogEvent::REQUESTED_UPLOAD_SAVE,
DialogEvent::RECEIVED_GET_UPLOAD_DETAILS_RESPONSE});
NavigateTo(kCreditCardUploadForm);
FillAndSubmitForm();
WaitForObservedEvent();
EXPECT_TRUE(
FindViewInBubbleById(DialogViewId::MAIN_CONTENT_VIEW_UPLOAD)->visible());
EXPECT_TRUE(FindViewInBubbleById(DialogViewId::FOOTNOTE_VIEW)->visible());
// Clicking the [X] close button should dismiss the bubble.
ClickOnCloseButton();
}
// Tests the upload save bubble. Ensures that the bubble does not surface the
// cardholder name textfield if it is not needed.
IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
Upload_ShouldNotRequestCardholderNameInHappyPath) {
// Enable the EditableCardholderName experiment.
scoped_feature_list_.InitWithFeatures(
// Enabled
{features::kAutofillUpstream,
features::kAutofillUpstreamEditableCardholderName},
// Disabled
{});
// Start sync.
harness_->SetupSync();
// Set up the Payments RPC.
SetUploadDetailsRpcPaymentsAccepts();
// Submitting the form should show the upload save bubble and legal footer.
// (Must wait for response from Payments before accessing the controller.)
ResetEventWaiterForSequence(
{DialogEvent::REQUESTED_UPLOAD_SAVE,
DialogEvent::RECEIVED_GET_UPLOAD_DETAILS_RESPONSE});
NavigateTo(kCreditCardUploadForm);
FillAndSubmitForm();
WaitForObservedEvent();
EXPECT_TRUE(
FindViewInBubbleById(DialogViewId::MAIN_CONTENT_VIEW_UPLOAD)->visible());
EXPECT_TRUE(FindViewInBubbleById(DialogViewId::FOOTNOTE_VIEW)->visible());
// Assert that cardholder name was not explicitly requested in the bubble.
EXPECT_FALSE(FindViewInBubbleById(DialogViewId::CARDHOLDER_NAME_TEXTFIELD));
}
// Tests the upload save bubble. Ensures that the bubble surfaces a textfield
// requesting cardholder name if cardholder name is missing.
IN_PROC_BROWSER_TEST_F(
SaveCardBubbleViewsFullFormBrowserTest,
Upload_SubmittingFormWithMissingNamesRequestsCardholderNameIfExpOn) {
// Enable the EditableCardholderName experiment.
scoped_feature_list_.InitWithFeatures(
// Enabled
{features::kAutofillUpstream,
features::kAutofillUpstreamEditableCardholderName},
// Disabled
{});
// Start sync.
harness_->SetupSync();
// Set up the Payments RPC.
SetUploadDetailsRpcPaymentsAccepts();
// Submitting the form should still show the upload save bubble and legal
// footer, along with a textfield specifically requesting the cardholder name.
// (Must wait for response from Payments before accessing the controller.)
ResetEventWaiterForSequence(
{DialogEvent::REQUESTED_UPLOAD_SAVE,
DialogEvent::RECEIVED_GET_UPLOAD_DETAILS_RESPONSE});
NavigateTo(kCreditCardUploadForm);
FillAndSubmitFormWithoutName();
WaitForObservedEvent();
EXPECT_TRUE(
FindViewInBubbleById(DialogViewId::MAIN_CONTENT_VIEW_UPLOAD)->visible());
EXPECT_TRUE(FindViewInBubbleById(DialogViewId::FOOTNOTE_VIEW)->visible());
EXPECT_TRUE(FindViewInBubbleById(DialogViewId::CARDHOLDER_NAME_TEXTFIELD));
}
// Tests the upload save bubble. Ensures that the bubble surfaces a textfield
// requesting cardholder name if cardholder name is conflicting.
IN_PROC_BROWSER_TEST_F(
SaveCardBubbleViewsFullFormBrowserTest,
Upload_SubmittingFormWithConflictingNamesRequestsCardholderNameIfExpOn) {
// Enable the EditableCardholderName experiment.
scoped_feature_list_.InitWithFeatures(
// Enabled
{features::kAutofillUpstream,
features::kAutofillUpstreamEditableCardholderName},
// Disabled
{});
// Start sync.
harness_->SetupSync();
// Set up the Payments RPC.
SetUploadDetailsRpcPaymentsAccepts();
// Submit first shipping address form with a conflicting name.
NavigateTo(kCreditCardAndShippingUploadForm);
FillAndSubmitFormWithConflictingName();
// Submitting the second form should still show the upload save bubble and
// legal footer, along with a textfield requesting the cardholder name.
// (Must wait for response from Payments before accessing the controller.)
ResetEventWaiterForSequence(
{DialogEvent::REQUESTED_UPLOAD_SAVE,
DialogEvent::RECEIVED_GET_UPLOAD_DETAILS_RESPONSE});
FillAndSubmitForm();
WaitForObservedEvent();
EXPECT_TRUE(
FindViewInBubbleById(DialogViewId::MAIN_CONTENT_VIEW_UPLOAD)->visible());
EXPECT_TRUE(FindViewInBubbleById(DialogViewId::FOOTNOTE_VIEW)->visible());
EXPECT_TRUE(FindViewInBubbleById(DialogViewId::CARDHOLDER_NAME_TEXTFIELD));
}
// Tests the upload save bubble. Ensures that if the cardholder name textfield
// is empty, the user is not allowed to click [Save] and close the dialog.
IN_PROC_BROWSER_TEST_F(
SaveCardBubbleViewsFullFormBrowserTest,
Upload_SaveButtonIsDisabledIfNoCardholderNameAndCardholderNameRequested) {
// Enable the EditableCardholderName experiment.
scoped_feature_list_.InitWithFeatures(
// Enabled
{features::kAutofillUpstream,
features::kAutofillUpstreamEditableCardholderName},
// Disabled
{});
// Start sync.
harness_->SetupSync();
// Set up the Payments RPC.
SetUploadDetailsRpcPaymentsAccepts();
// Submitting the form should still show the upload save bubble and legal
// footer, along with a textfield specifically requesting the cardholder name.
// (Must wait for response from Payments before accessing the controller.)
ResetEventWaiterForSequence(
{DialogEvent::REQUESTED_UPLOAD_SAVE,
DialogEvent::RECEIVED_GET_UPLOAD_DETAILS_RESPONSE});
NavigateTo(kCreditCardUploadForm);
FillAndSubmitFormWithoutName();
WaitForObservedEvent();
EXPECT_TRUE(
FindViewInBubbleById(DialogViewId::MAIN_CONTENT_VIEW_UPLOAD)->visible());
EXPECT_TRUE(FindViewInBubbleById(DialogViewId::FOOTNOTE_VIEW)->visible());
EXPECT_TRUE(FindViewInBubbleById(DialogViewId::CARDHOLDER_NAME_TEXTFIELD));
// Clearing out the cardholder name textfield should disable the [Save]
// button.
views::Textfield* cardholder_name_textfield = static_cast<views::Textfield*>(
FindViewInBubbleById(DialogViewId::CARDHOLDER_NAME_TEXTFIELD));
cardholder_name_textfield->InsertOrReplaceText(base::ASCIIToUTF16(""));
views::LabelButton* save_button = static_cast<views::LabelButton*>(
FindViewInBubbleById(DialogViewId::OK_BUTTON));
EXPECT_EQ(save_button->state(),
views::LabelButton::ButtonState::STATE_DISABLED);
// Setting a cardholder name should enable the [Save] button.
cardholder_name_textfield->InsertOrReplaceText(
base::ASCIIToUTF16("John Smith"));
EXPECT_EQ(save_button->state(),
views::LabelButton::ButtonState::STATE_NORMAL);
}
// Tests the upload save bubble. Ensures that if cardholder name is explicitly
// requested, filling it and clicking [Save] closes the dialog.
IN_PROC_BROWSER_TEST_F(
SaveCardBubbleViewsFullFormBrowserTest,
Upload_EnteringCardholderNameAndClickingSaveClosesBubbleIfCardholderNameRequested) {
// Enable the EditableCardholderName experiment.
scoped_feature_list_.InitWithFeatures(
// Enabled
{features::kAutofillUpstream,
features::kAutofillUpstreamEditableCardholderName},
// Disabled
{});
// Start sync.
harness_->SetupSync();
// Set up the Payments RPC.
SetUploadDetailsRpcPaymentsAccepts();
// Submitting the form should still show the upload save bubble and legal
// footer, along with a textfield specifically requesting the cardholder name.
// (Must wait for response from Payments before accessing the controller.)
ResetEventWaiterForSequence(
{DialogEvent::REQUESTED_UPLOAD_SAVE,
DialogEvent::RECEIVED_GET_UPLOAD_DETAILS_RESPONSE});
NavigateTo(kCreditCardUploadForm);
FillAndSubmitFormWithoutName();
WaitForObservedEvent();
EXPECT_TRUE(
FindViewInBubbleById(DialogViewId::MAIN_CONTENT_VIEW_UPLOAD)->visible());
EXPECT_TRUE(FindViewInBubbleById(DialogViewId::FOOTNOTE_VIEW)->visible());
EXPECT_TRUE(FindViewInBubbleById(DialogViewId::CARDHOLDER_NAME_TEXTFIELD));
// Entering a cardholder name and clicking [Save] should accept and close
// the bubble, then send an UploadCardRequest to Google Payments.
ResetEventWaiterForSequence({DialogEvent::SENT_UPLOAD_CARD_REQUEST});
views::Textfield* cardholder_name_textfield = static_cast<views::Textfield*>(
FindViewInBubbleById(DialogViewId::CARDHOLDER_NAME_TEXTFIELD));
cardholder_name_textfield->InsertOrReplaceText(
base::ASCIIToUTF16("John Smith"));
base::HistogramTester histogram_tester;
ClickOnDialogViewWithIdAndWait(DialogViewId::OK_BUTTON);
// UMA should have recorded bubble acceptance for both regular save UMA and
// the ".RequestingCardholderName" subhistogram.
histogram_tester.ExpectUniqueSample(
"Autofill.SaveCreditCardPrompt.Upload.FirstShow",
AutofillMetrics::SAVE_CARD_PROMPT_END_ACCEPTED, 1);
histogram_tester.ExpectUniqueSample(
"Autofill.SaveCreditCardPrompt.Upload.FirstShow.RequestingCardholderName",
AutofillMetrics::SAVE_CARD_PROMPT_END_ACCEPTED, 1);
}
// Tests the upload save bubble. Ensures that if cardholder name is explicitly
// requested, it is prefilled with the name from the user's Google Account.
IN_PROC_BROWSER_TEST_F(
SaveCardBubbleViewsFullFormBrowserTest,
Upload_RequestedCardholderNameTextfieldIsPrefilledWithFocusName) {
// Enable the EditableCardholderName experiment.
scoped_feature_list_.InitWithFeatures(
// Enabled
{features::kAutofillUpstream,
features::kAutofillUpstreamEditableCardholderName},
// Disabled
{});
// Start sync.
harness_->SetupSync();
// Set the user's full name.
SetAccountFullName("John Smith");
// Set up the Payments RPC.
SetUploadDetailsRpcPaymentsAccepts();
// Submitting the form should show the upload save bubble, along with a
// textfield specifically requesting the cardholder name.
// (Must wait for response from Payments before accessing the controller.)
ResetEventWaiterForSequence(
{DialogEvent::REQUESTED_UPLOAD_SAVE,
DialogEvent::RECEIVED_GET_UPLOAD_DETAILS_RESPONSE});
base::HistogramTester histogram_tester;
NavigateTo(kCreditCardUploadForm);
FillAndSubmitFormWithoutName();
WaitForObservedEvent();
EXPECT_TRUE(FindViewInBubbleById(DialogViewId::CARDHOLDER_NAME_TEXTFIELD));
// The textfield should be prefilled with the name on the user's Google
// Account, and UMA should have logged its value's existence. Because the
// textfield has a value, the tooltip explaining that the name came from the
// user's Google Account should also be visible.
views::Textfield* cardholder_name_textfield = static_cast<views::Textfield*>(
FindViewInBubbleById(DialogViewId::CARDHOLDER_NAME_TEXTFIELD));
EXPECT_EQ(cardholder_name_textfield->text(),
base::ASCIIToUTF16("John Smith"));
histogram_tester.ExpectUniqueSample(
"Autofill.SaveCardCardholderNamePrefilled", true, 1);
EXPECT_TRUE(FindViewInBubbleById(DialogViewId::CARDHOLDER_NAME_TOOLTIP));
}
// Tests the upload save bubble. Ensures that if cardholder name is explicitly
// requested but the name on the user's Google Account is unable to be fetched
// for any reason, the textfield is left blank.
IN_PROC_BROWSER_TEST_F(
SaveCardBubbleViewsFullFormBrowserTest,
Upload_RequestedCardholderNameTextfieldIsNotPrefilledWithFocusNameIfMissing) {
// Enable the EditableCardholderName experiment.
scoped_feature_list_.InitWithFeatures(
// Enabled
{features::kAutofillUpstream,
features::kAutofillUpstreamEditableCardholderName},
// Disabled
{});
// Start sync.
harness_->SetupSync();
// Set up the Payments RPC.
SetUploadDetailsRpcPaymentsAccepts();
// Don't sign the user in. In this case, the user's Account cannot be fetched
// and their name is not available.
// Submitting the form should show the upload save bubble, along with a
// textfield specifically requesting the cardholder name.
// (Must wait for response from Payments before accessing the controller.)
ResetEventWaiterForSequence(
{DialogEvent::REQUESTED_UPLOAD_SAVE,
DialogEvent::RECEIVED_GET_UPLOAD_DETAILS_RESPONSE});
base::HistogramTester histogram_tester;
NavigateTo(kCreditCardUploadForm);
FillAndSubmitFormWithoutName();
WaitForObservedEvent();
EXPECT_TRUE(FindViewInBubbleById(DialogViewId::CARDHOLDER_NAME_TEXTFIELD));
// The textfield should be blank, and UMA should have logged its value's
// absence. Because the textfield is blank, the tooltip explaining that the
// name came from the user's Google Account should NOT be visible.
views::Textfield* cardholder_name_textfield = static_cast<views::Textfield*>(
FindViewInBubbleById(DialogViewId::CARDHOLDER_NAME_TEXTFIELD));
EXPECT_TRUE(cardholder_name_textfield->text().empty());
histogram_tester.ExpectUniqueSample(
"Autofill.SaveCardCardholderNamePrefilled", false, 1);
EXPECT_FALSE(FindViewInBubbleById(DialogViewId::CARDHOLDER_NAME_TOOLTIP));
}
// Tests the upload save bubble. Ensures that if cardholder name is explicitly
// requested and the user accepts the dialog without changing it, the correct
// metric is logged.
IN_PROC_BROWSER_TEST_F(
SaveCardBubbleViewsFullFormBrowserTest,
Upload_CardholderNameRequested_SubmittingPrefilledValueLogsUneditedMetric) {
// Enable the EditableCardholderName experiment.
scoped_feature_list_.InitWithFeatures(
// Enabled
{features::kAutofillUpstream,
features::kAutofillUpstreamEditableCardholderName},
// Disabled
{});
// Start sync.
harness_->SetupSync();
// Set the user's full name.
SetAccountFullName("John Smith");
// Set up the Payments RPC.
SetUploadDetailsRpcPaymentsAccepts();
// Submitting the form should show the upload save bubble, along with a
// textfield specifically requesting the cardholder name.
// (Must wait for response from Payments before accessing the controller.)
ResetEventWaiterForSequence(
{DialogEvent::REQUESTED_UPLOAD_SAVE,
DialogEvent::RECEIVED_GET_UPLOAD_DETAILS_RESPONSE});
NavigateTo(kCreditCardUploadForm);
FillAndSubmitFormWithoutName();
WaitForObservedEvent();
EXPECT_TRUE(FindViewInBubbleById(DialogViewId::CARDHOLDER_NAME_TEXTFIELD));
// Clicking [Save] should accept and close the bubble, logging that the name
// was not edited.
ResetEventWaiterForSequence({DialogEvent::SENT_UPLOAD_CARD_REQUEST});
base::HistogramTester histogram_tester;
ClickOnDialogViewWithIdAndWait(DialogViewId::OK_BUTTON);
histogram_tester.ExpectUniqueSample(
"Autofill.SaveCardCardholderNameWasEdited", false, 1);
}
// Tests the upload save bubble. Ensures that if cardholder name is explicitly
// requested and the user accepts the dialog after changing it, the correct
// metric is logged.
IN_PROC_BROWSER_TEST_F(
SaveCardBubbleViewsFullFormBrowserTest,
Upload_CardholderNameRequested_SubmittingChangedValueLogsEditedMetric) {
// Enable the EditableCardholderName experiment.
scoped_feature_list_.InitWithFeatures(
// Enabled
{features::kAutofillUpstream,
features::kAutofillUpstreamEditableCardholderName},
// Disabled
{});
// Start sync.
harness_->SetupSync();
// Set the user's full name.
SetAccountFullName("John Smith");
// Set up the Payments RPC.
SetUploadDetailsRpcPaymentsAccepts();
// Submitting the form should show the upload save bubble, along with a
// textfield specifically requesting the cardholder name.
// (Must wait for response from Payments before accessing the controller.)
ResetEventWaiterForSequence(
{DialogEvent::REQUESTED_UPLOAD_SAVE,
DialogEvent::RECEIVED_GET_UPLOAD_DETAILS_RESPONSE});
NavigateTo(kCreditCardUploadForm);
FillAndSubmitFormWithoutName();
WaitForObservedEvent();
EXPECT_TRUE(FindViewInBubbleById(DialogViewId::CARDHOLDER_NAME_TEXTFIELD));
// Changing the name then clicking [Save] should accept and close the bubble,
// logging that the name was edited.
ResetEventWaiterForSequence({DialogEvent::SENT_UPLOAD_CARD_REQUEST});
views::Textfield* cardholder_name_textfield = static_cast<views::Textfield*>(
FindViewInBubbleById(DialogViewId::CARDHOLDER_NAME_TEXTFIELD));
cardholder_name_textfield->InsertOrReplaceText(
base::ASCIIToUTF16("Jane Doe"));
base::HistogramTester histogram_tester;
ClickOnDialogViewWithIdAndWait(DialogViewId::OK_BUTTON);
histogram_tester.ExpectUniqueSample(
"Autofill.SaveCardCardholderNameWasEdited", true, 1);
}
// Tests the upload save bubble. Ensures that if cardholder name is explicitly
// requested but the AutofillUpstreamBlankCardholderNameField experiment is
// active, the textfield is NOT prefilled even though the user's Google Account
// name is available.
IN_PROC_BROWSER_TEST_F(
SaveCardBubbleViewsFullFormBrowserTest,
Upload_CardholderNameNotPrefilledIfBlankNameExperimentEnabled) {
// Enable the EditableCardholderName and BlankCardholderNameField experiments.
scoped_feature_list_.InitWithFeatures(
{features::kAutofillUpstream,
features::kAutofillUpstreamEditableCardholderName,
features::kAutofillUpstreamBlankCardholderNameField},
// Disabled
{});
// Start sync.
harness_->SetupSync();
// Set the user's full name.
SetAccountFullName("John Smith");
// Set up the Payments RPC.
SetUploadDetailsRpcPaymentsAccepts();
// Submitting the form should show the upload save bubble, along with a
// textfield specifically requesting the cardholder name.
// (Must wait for response from Payments before accessing the controller.)
ResetEventWaiterForSequence(
{DialogEvent::REQUESTED_UPLOAD_SAVE,
DialogEvent::RECEIVED_GET_UPLOAD_DETAILS_RESPONSE});
base::HistogramTester histogram_tester;
NavigateTo(kCreditCardUploadForm);
FillAndSubmitFormWithoutName();
WaitForObservedEvent();
EXPECT_TRUE(FindViewInBubbleById(DialogViewId::CARDHOLDER_NAME_TEXTFIELD));
// The textfield should be blank, and UMA should have logged its value's
// absence. Because the textfield is blank, the tooltip explaining that the
// name came from the user's Google Account should NOT be visible.
views::Textfield* cardholder_name_textfield = static_cast<views::Textfield*>(
FindViewInBubbleById(DialogViewId::CARDHOLDER_NAME_TEXTFIELD));
EXPECT_TRUE(cardholder_name_textfield->text().empty());
histogram_tester.ExpectUniqueSample(
"Autofill.SaveCardCardholderNamePrefilled", false, 1);
EXPECT_FALSE(FindViewInBubbleById(DialogViewId::CARDHOLDER_NAME_TOOLTIP));
}
// TODO(jsaul): Only *part* of the legal message StyledLabel is clickable, and
// the NOTREACHED() in SaveCardBubbleViews::StyledLabelLinkClicked
// prevents us from being able to click it unless we know the exact
// gfx::Range of the link. When/if that can be worked around,
// create an Upload_ClickingTosLinkClosesBubble test.
// Tests the upload save logic. Ensures that Chrome offers a local save when the
// data is complete, even if Payments rejects the data.
IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
Logic_ShouldOfferLocalSaveIfPaymentsDeclines) {
scoped_feature_list_.InitAndEnableFeature(features::kAutofillUpstream);
// Start sync.
harness_->SetupSync();
// Set up the Payments RPC.
SetUploadDetailsRpcPaymentsDeclines();
// Submitting the form and having Payments decline offering to save should
// show the local save bubble.
// (Must wait for response from Payments before accessing the controller.)
ResetEventWaiterForSequence(
{DialogEvent::REQUESTED_UPLOAD_SAVE,
DialogEvent::RECEIVED_GET_UPLOAD_DETAILS_RESPONSE,
DialogEvent::OFFERED_LOCAL_SAVE});
NavigateTo(kCreditCardUploadForm);
FillAndSubmitForm();
WaitForObservedEvent();
EXPECT_TRUE(
FindViewInBubbleById(DialogViewId::MAIN_CONTENT_VIEW_LOCAL)->visible());
}
// Tests the upload save logic. Ensures that Chrome offers a local save when the
// data is complete, even if the Payments upload fails unexpectedly.
IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
Logic_ShouldOfferLocalSaveIfPaymentsFails) {
scoped_feature_list_.InitAndEnableFeature(features::kAutofillUpstream);
// Start sync.
harness_->SetupSync();
// Set up the Payments RPC.
SetUploadDetailsRpcServerError();
// Submitting the form and having the call to Payments fail should show the
// local save bubble.
// (Must wait for response from Payments before accessing the controller.)
ResetEventWaiterForSequence(
{DialogEvent::REQUESTED_UPLOAD_SAVE,
DialogEvent::RECEIVED_GET_UPLOAD_DETAILS_RESPONSE,
DialogEvent::OFFERED_LOCAL_SAVE});
NavigateTo(kCreditCardUploadForm);
FillAndSubmitForm();
WaitForObservedEvent();
EXPECT_TRUE(
FindViewInBubbleById(DialogViewId::MAIN_CONTENT_VIEW_LOCAL)->visible());
}
// Tests the upload save logic. Ensures that Chrome delegates the offer-to-save
// call to Payments, and offers to upload save the card if Payments allows it.
IN_PROC_BROWSER_TEST_F(
SaveCardBubbleViewsFullFormBrowserTest,
Logic_CanOfferToSaveEvenIfNothingFoundIfPaymentsAccepts) {
scoped_feature_list_.InitAndEnableFeature(features::kAutofillUpstream);
// Start sync.
harness_->SetupSync();
// Set up the Payments RPC.
SetUploadDetailsRpcPaymentsAccepts();
// Submitting the form, even with only card number and expiration date, should
// start the flow of asking Payments if Chrome should offer to save the card
// to Google. In this case, Payments says yes, and the offer to save bubble
// should be shown.
ResetEventWaiterForSequence(
{DialogEvent::REQUESTED_UPLOAD_SAVE,
DialogEvent::RECEIVED_GET_UPLOAD_DETAILS_RESPONSE});
NavigateTo(kCreditCardUploadForm);
FillAndSubmitFormWithCardDetailsOnly();
WaitForObservedEvent();
EXPECT_TRUE(
FindViewInBubbleById(DialogViewId::MAIN_CONTENT_VIEW_UPLOAD)->visible());
EXPECT_TRUE(FindViewInBubbleById(DialogViewId::FOOTNOTE_VIEW)->visible());
}
// Tests the upload save logic. Ensures that Chrome delegates the offer-to-save
// call to Payments, and still does not surface the offer to upload save dialog
// if Payments declines it.
IN_PROC_BROWSER_TEST_F(
SaveCardBubbleViewsFullFormBrowserTest,
Logic_ShouldNotOfferToSaveIfNothingFoundAndPaymentsDeclines) {
scoped_feature_list_.InitAndEnableFeature(features::kAutofillUpstream);
// Start sync.
harness_->SetupSync();
// Set up the Payments RPC.
SetUploadDetailsRpcPaymentsDeclines();
// Submitting the form, even with only card number and expiration date, should
// start the flow of asking Payments if Chrome should offer to save the card
// to Google. In this case, Payments says no, so the offer to save bubble
// should not be shown.
ResetEventWaiterForSequence(
{DialogEvent::REQUESTED_UPLOAD_SAVE,
DialogEvent::RECEIVED_GET_UPLOAD_DETAILS_RESPONSE});
NavigateTo(kCreditCardUploadForm);
FillAndSubmitFormWithCardDetailsOnly();
WaitForObservedEvent();
EXPECT_FALSE(GetSaveCardBubbleViews());
}
// Tests the upload save logic. Ensures that Chrome lets Payments decide whether
// upload save should be offered, even if CVC is not detected.
IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
Logic_ShouldAttemptToOfferToSaveIfCvcNotFound) {
scoped_feature_list_.InitAndEnableFeature(features::kAutofillUpstream);
// Start sync.
harness_->SetupSync();
// Submitting the form should still start the flow of asking Payments if
// Chrome should offer to save the card to Google, even though CVC is missing.
ResetEventWaiterForSequence({DialogEvent::REQUESTED_UPLOAD_SAVE});
NavigateTo(kCreditCardUploadForm);
FillAndSubmitFormWithoutCvc();
WaitForObservedEvent();
}
// Tests the upload save logic. Ensures that Chrome lets Payments decide whether
// upload save should be offered, even if the detected CVC is invalid.
IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
Logic_ShouldAttemptToOfferToSaveIfInvalidCvcFound) {
scoped_feature_list_.InitAndEnableFeature(features::kAutofillUpstream);
// Start sync.
harness_->SetupSync();
// Submitting the form should still start the flow of asking Payments if
// Chrome should offer to save the card to Google, even though the provided
// CVC is invalid.
ResetEventWaiterForSequence({DialogEvent::REQUESTED_UPLOAD_SAVE});
NavigateTo(kCreditCardUploadForm);
FillAndSubmitFormWithInvalidCvc();
WaitForObservedEvent();
}
// Tests the upload save logic. Ensures that Chrome lets Payments decide whether
// upload save should be offered, even if address/cardholder name is not
// detected.
IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
Logic_ShouldAttemptToOfferToSaveIfNameNotFound) {
scoped_feature_list_.InitAndEnableFeature(features::kAutofillUpstream);
// Start sync.
harness_->SetupSync();
// Submitting the form should still start the flow of asking Payments if
// Chrome should offer to save the card to Google, even though name is
// missing.
ResetEventWaiterForSequence({DialogEvent::REQUESTED_UPLOAD_SAVE});
NavigateTo(kCreditCardUploadForm);
FillAndSubmitFormWithoutName();
WaitForObservedEvent();
}
// Tests the upload save logic. Ensures that Chrome lets Payments decide whether
// upload save should be offered, even if multiple conflicting names are
// detected.
IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
Logic_ShouldAttemptToOfferToSaveIfNamesConflict) {
scoped_feature_list_.InitAndEnableFeature(features::kAutofillUpstream);
// Start sync.
harness_->SetupSync();
// Submit first shipping address form with a conflicting name.
NavigateTo(kCreditCardAndShippingUploadForm);
FillAndSubmitFormWithConflictingName();
// Submitting the form should still start the flow of asking Payments if
// Chrome should offer to save the card to Google, even though the name
// conflicts with the previous form.
ResetEventWaiterForSequence({DialogEvent::REQUESTED_UPLOAD_SAVE});
FillAndSubmitForm();
WaitForObservedEvent();
}
// Tests the upload save logic. Ensures that Chrome lets Payments decide whether
// upload save should be offered, even if billing address is not detected.
IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
Logic_ShouldAttemptToOfferToSaveIfAddressNotFound) {
scoped_feature_list_.InitAndEnableFeature(features::kAutofillUpstream);
// Start sync.
harness_->SetupSync();
// Submitting the form should still start the flow of asking Payments if
// Chrome should offer to save the card to Google, even though billing address
// is missing.
ResetEventWaiterForSequence({DialogEvent::REQUESTED_UPLOAD_SAVE});
NavigateTo(kCreditCardUploadForm);
FillAndSubmitFormWithoutAddress();
WaitForObservedEvent();
}
// Tests the upload save logic. Ensures that Chrome lets Payments decide whether
// upload save should be offered, even if multiple conflicting billing address
// postal codes are detected.
IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
Logic_ShouldAttemptToOfferToSaveIfPostalCodesConflict) {
scoped_feature_list_.InitAndEnableFeature(features::kAutofillUpstream);
// Start sync.
harness_->SetupSync();
// Submit first shipping address form with a conflicting postal code.
NavigateTo(kCreditCardAndShippingUploadForm);
FillAndSubmitFormWithConflictingPostalCode();
// Submitting the form should still start the flow of asking Payments if
// Chrome should offer to save the card to Google, even though the postal code
// conflicts with the previous form.
ResetEventWaiterForSequence({DialogEvent::REQUESTED_UPLOAD_SAVE});
FillAndSubmitForm();
WaitForObservedEvent();
}
// Tests UMA logging for the upload save bubble. Ensures that if the user
// declines upload, Autofill.UploadAcceptedCardOrigin is not logged.
IN_PROC_BROWSER_TEST_F(
SaveCardBubbleViewsFullFormBrowserTest,
Upload_DecliningUploadDoesNotLogUserAcceptedCardOriginUMA) {
scoped_feature_list_.InitAndEnableFeature(features::kAutofillUpstream);
// Start sync.
harness_->SetupSync();
// Set up the Payments RPC.
SetUploadDetailsRpcPaymentsAccepts();
// Submitting the form should show the upload save bubble and legal footer.
// (Must wait for response from Payments before accessing the controller.)
base::HistogramTester histogram_tester;
ResetEventWaiterForSequence(
{DialogEvent::REQUESTED_UPLOAD_SAVE,
DialogEvent::RECEIVED_GET_UPLOAD_DETAILS_RESPONSE});
NavigateTo(kCreditCardUploadForm);
FillAndSubmitForm();
WaitForObservedEvent();
EXPECT_TRUE(
FindViewInBubbleById(DialogViewId::MAIN_CONTENT_VIEW_UPLOAD)->visible());
EXPECT_TRUE(FindViewInBubbleById(DialogViewId::FOOTNOTE_VIEW)->visible());
// Clicking the [X] close button should dismiss the bubble.
ClickOnCloseButton();
// Ensure that UMA was logged correctly.
histogram_tester.ExpectUniqueSample(
"Autofill.UploadOfferedCardOrigin",
AutofillMetrics::OFFERING_UPLOAD_OF_NEW_CARD, 1);
histogram_tester.ExpectTotalCount("Autofill.UploadAcceptedCardOrigin", 0);
}
// Tests the upload save bubble. Ensures that the bubble surfaces a pair of
// dropdowns requesting expiration date if expiration date is missing.
IN_PROC_BROWSER_TEST_F(
SaveCardBubbleViewsFullFormBrowserTest,
Upload_SubmittingFormWithMissingExpirationDateRequestsExpirationDate) {
SetUpForEditableExpirationDate();
FillAndSubmitFormWithoutExpirationDate();
WaitForObservedEvent();
VerifyExpirationDateDropdownsAreVisible();
}
// Tests the upload save bubble. Ensures that the bubble surfaces a pair of
// dropdowns requesting expiration date if expiration date is expired.
IN_PROC_BROWSER_TEST_F(
SaveCardBubbleViewsFullFormBrowserTest,
Upload_SubmittingFormWithExpiredExpirationDateRequestsExpirationDate) {
SetUpForEditableExpirationDate();
FillAndSubmitFormWithSpecificExpirationDate("08", "2000");
WaitForObservedEvent();
VerifyExpirationDateDropdownsAreVisible();
}
// Tests the upload save bubble. Ensures that the bubble is not shown when
// expiration date is passed, but the flag is disabled.
IN_PROC_BROWSER_TEST_F(
SaveCardBubbleViewsFullFormBrowserTest,
Logic_ShouldNotOfferToSaveIfSubmittingExpiredExpirationDateAndExpOff) {
// Disable the EditableExpirationDate experiment.
scoped_feature_list_.InitWithFeatures(
// Enabled
{features::kAutofillUpstream},
// Disabled
{features::kAutofillUpstreamEditableExpirationDate});
// The credit card will not be imported if the expiration date is expired and
// experiment is off.
NavigateTo(kCreditCardUploadForm);
FillAndSubmitFormWithSpecificExpirationDate("08", "2000");
EXPECT_FALSE(GetSaveCardBubbleViews());
}
// Tests the upload save bubble. Ensures that the bubble is not shown when
// expiration date is missing, but the flag is disabled.
IN_PROC_BROWSER_TEST_F(
SaveCardBubbleViewsFullFormBrowserTest,
Logic_ShouldNotOfferToSaveIfMissingExpirationDateAndExpOff) {
// Disable the EditableExpirationDate experiment.
scoped_feature_list_.InitWithFeatures(
// Enabled
{features::kAutofillUpstream},
// Disabled
{features::kAutofillUpstreamEditableExpirationDate});
// The credit card will not be imported if there is no expiration date and
// experiment is off.
NavigateTo(kCreditCardUploadForm);
FillAndSubmitFormWithoutExpirationDate();
EXPECT_FALSE(GetSaveCardBubbleViews());
}
// Tests the upload save bubble. Ensures that the bubble does not surface the
// expiration date dropdowns if it is not needed.
IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
Upload_ShouldNotRequestExpirationDateInHappyPath) {
SetUpForEditableExpirationDate();
FillAndSubmitForm();
WaitForObservedEvent();
EXPECT_TRUE(
FindViewInBubbleById(DialogViewId::MAIN_CONTENT_VIEW_UPLOAD)->visible());
EXPECT_TRUE(FindViewInBubbleById(DialogViewId::FOOTNOTE_VIEW)->visible());
EXPECT_TRUE(
FindViewInBubbleById(DialogViewId::EXPIRATION_DATE_LABEL)->visible());
// Assert that expiration date was not explicitly requested in the bubble.
EXPECT_FALSE(FindViewInBubbleById(DialogViewId::EXPIRATION_DATE_VIEW));
EXPECT_FALSE(
FindViewInBubbleById(DialogViewId::EXPIRATION_DATE_DROPBOX_YEAR));
EXPECT_FALSE(
FindViewInBubbleById(DialogViewId::EXPIRATION_DATE_DROPBOX_MONTH));
}
// Tests the upload save bubble. Ensures that if the expiration date drop down
// box is changing, [Save] button will change status correctly.
IN_PROC_BROWSER_TEST_F(
SaveCardBubbleViewsFullFormBrowserTest,
Upload_SaveButtonStatusResetBetweenExpirationDateSelectionChanges) {
SetUpForEditableExpirationDate();
FillAndSubmitFormWithoutExpirationDate();
WaitForObservedEvent();
VerifyExpirationDateDropdownsAreVisible();
// [Save] button is disabled by default when requesting expiration date,
// because there are no preselected values in the dropdown lists.
views::LabelButton* save_button = static_cast<views::LabelButton*>(
FindViewInBubbleById(DialogViewId::OK_BUTTON));
EXPECT_EQ(save_button->state(),
views::LabelButton::ButtonState::STATE_DISABLED);
// Selecting only month or year will disable [Save] button.
year_input()->SetSelectedRow(2);
EXPECT_EQ(save_button->state(),
views::LabelButton::ButtonState::STATE_DISABLED);
year_input()->SetSelectedRow(0);
month_input()->SetSelectedRow(2);
EXPECT_EQ(save_button->state(),
views::LabelButton::ButtonState::STATE_DISABLED);
// Selecting both month and year will enable [Save] button.
month_input()->SetSelectedRow(2);
year_input()->SetSelectedRow(2);
EXPECT_EQ(save_button->state(),
views::LabelButton::ButtonState::STATE_NORMAL);
}
// Tests the upload save bubble. Ensures that if the user is selecting an
// expired expiration date, it is not allowed to click [Save].
IN_PROC_BROWSER_TEST_F(
SaveCardBubbleViewsFullFormBrowserTest,
Upload_SaveButtonIsDisabledIfExpiredExpirationDateAndExpirationDateRequested) {
SetUpForEditableExpirationDate();
FillAndSubmitFormWithoutExpirationDate();
WaitForObservedEvent();
VerifyExpirationDateDropdownsAreVisible();
views::LabelButton* save_button = static_cast<views::LabelButton*>(
FindViewInBubbleById(DialogViewId::OK_BUTTON));
// Set now to next month. Setting test_clock will not affect the dropdown to
// be selected, so selecting the current January will always be expired.
autofill::TestAutofillClock test_clock;
test_clock.SetNow(base::Time::Now());
test_clock.Advance(base::TimeDelta::FromDays(40));
// Selecting expired date will disable [Save] button.
month_input()->SetSelectedRow(1);
year_input()->SetSelectedRow(1);
EXPECT_EQ(save_button->state(),
views::LabelButton::ButtonState::STATE_DISABLED);
}
// Tests the upload save bubble. Ensures that the bubble surfaces a pair of
// dropdowns requesting expiration date with year pre-populated if year is valid
// but month is missing.
IN_PROC_BROWSER_TEST_F(
SaveCardBubbleViewsFullFormBrowserTest,
Upload_SubmittingFormWithMissingExpirationDateMonthAndWithValidYear) {
SetUpForEditableExpirationDate();
// Submit the form with a year value, but not a month value.
FillAndSubmitFormWithExpirationYearOnly(test::NextYear());
WaitForObservedEvent();
VerifyExpirationDateDropdownsAreVisible();
// Ensure the next year is pre-populated but month is not checked.
EXPECT_EQ(0, month_input()->selected_index());
EXPECT_EQ(base::ASCIIToUTF16(test::NextYear()),
year_input()->GetTextForRow(year_input()->selected_index()));
}
// Tests the upload save bubble. Ensures that the bubble surfaces a pair of
// dropdowns requesting expiration date with month pre-populated if month is
// detected but year is missing.
IN_PROC_BROWSER_TEST_F(
SaveCardBubbleViewsFullFormBrowserTest,
Upload_SubmittingFormWithMissingExpirationDateYearAndWithMonth) {
SetUpForEditableExpirationDate();
// Submit the form with a month value, but not a year value.
FillAndSubmitFormWithExpirationMonthOnly("12");
WaitForObservedEvent();
VerifyExpirationDateDropdownsAreVisible();
// Ensure the December is pre-populated but year is not checked.
EXPECT_EQ(base::ASCIIToUTF16("12"),
month_input()->GetTextForRow(month_input()->selected_index()));
EXPECT_EQ(0, year_input()->selected_index());
}
// Tests the upload save bubble. Ensures that the bubble surfaces a pair of
// dropdowns requesting expiration date if month is missing and year is detected
// but out of the range of dropdown.
IN_PROC_BROWSER_TEST_F(
SaveCardBubbleViewsFullFormBrowserTest,
Upload_SubmittingFormWithExpirationDateMonthAndWithYearIsOutOfRange) {
SetUpForEditableExpirationDate();
// Fill form but with an expiration year ten years in the future which is out
// of the range of |year_input_dropdown_|.
FillAndSubmitFormWithExpirationYearOnly(test::TenYearsFromNow());
WaitForObservedEvent();
VerifyExpirationDateDropdownsAreVisible();
// Ensure no pre-populated expiration date.
EXPECT_EQ(0, month_input()->selected_index());
EXPECT_EQ(0, year_input()->GetSelectedRow());
}
// Tests the upload save bubble. Ensures that the bubble surfaces a pair of
// dropdowns requesting expiration date if expiration date month is missing and
// year is detected but passed.
IN_PROC_BROWSER_TEST_F(
SaveCardBubbleViewsFullFormBrowserTest,
Upload_SubmittingFormWithExpirationDateMonthAndYearExpired) {
SetUpForEditableExpirationDate();
// Fill form with a valid month but a passed year.
FillAndSubmitFormWithSpecificExpirationDate("08", "2000");
WaitForObservedEvent();
VerifyExpirationDateDropdownsAreVisible();
// Ensure no pre-populated expiration date.
EXPECT_EQ(base::ASCIIToUTF16("08"),
month_input()->GetTextForRow(month_input()->selected_index()));
EXPECT_EQ(0, year_input()->GetSelectedRow());
}
// Tests the upload save bubble. Ensures that the bubble surfaces a pair of
// dropdowns requesting expiration date if expiration date is expired but is
// current year.
IN_PROC_BROWSER_TEST_F(
SaveCardBubbleViewsFullFormBrowserTest,
Upload_SubmittingFormWithExpirationDateMonthAndCurrentYear) {
SetUpForEditableExpirationDate();
const base::Time kJune2017 = base::Time::FromDoubleT(1497552271);
autofill::TestAutofillClock test_clock;
test_clock.SetNow(kJune2017);
// Fill form with a valid month but a passed year.
FillAndSubmitFormWithSpecificExpirationDate("03", "2017");
WaitForObservedEvent();
VerifyExpirationDateDropdownsAreVisible();
// Ensure pre-populated expiration date.
EXPECT_EQ(base::ASCIIToUTF16("03"),
month_input()->GetTextForRow(month_input()->selected_index()));
EXPECT_EQ(base::ASCIIToUTF16("2017"),
year_input()->GetTextForRow(year_input()->selected_index()));
}
// TODO(crbug.com/884817): Investigate combining local vs. upload tests using a
// boolean to branch local vs. upload logic.
// Tests StrikeDatabase interaction with the local save bubble. Ensures that no
// strikes are added if the feature flag is disabled.
IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
StrikeDatabase_Local_StrikeNotAddedIfExperimentFlagOff) {
// Disable the the SaveCreditCardUsesStrikeSystem experiments.
scoped_feature_list_.InitWithFeatures(
// Enabled
{features::kAutofillSaveCardImprovedUserConsent},
// Disabled
{features::kAutofillSaveCreditCardUsesStrikeSystem,
features::kAutofillSaveCreditCardUsesStrikeSystemV2});
// Submitting the form and having Payments decline offering to save should
// show the local save bubble.
// (Must wait for response from Payments before accessing the controller.)
ResetEventWaiterForSequence({DialogEvent::OFFERED_LOCAL_SAVE});
NavigateTo(kCreditCardUploadForm);
FillAndSubmitForm();
WaitForObservedEvent();
EXPECT_TRUE(
FindViewInBubbleById(DialogViewId::MAIN_CONTENT_VIEW_LOCAL)->visible());
base::HistogramTester histogram_tester;
ClickOnCancelButton();
// Ensure that no strike was added because the feature is disabled.
histogram_tester.ExpectTotalCount(
"Autofill.StrikeDatabase.NthStrikeAdded.CreditCardSave", 0);
}
// Tests StrikeDatabase interaction with the upload save bubble. Ensures that no
// strikes are added if the feature flag is disabled.
IN_PROC_BROWSER_TEST_F(
SaveCardBubbleViewsFullFormBrowserTest,
StrikeDatabase_Upload_StrikeNotAddedIfExperimentFlagOff) {
// Disable the the SaveCreditCardUsesStrikeSystem experiments.
scoped_feature_list_.InitWithFeatures(
// Enabled
{features::kAutofillUpstream,
features::kAutofillSaveCardImprovedUserConsent},
// Disabled
{features::kAutofillSaveCreditCardUsesStrikeSystem,
features::kAutofillSaveCreditCardUsesStrikeSystemV2});
// Start sync.
harness_->SetupSync();
// Set up the Payments RPC.
SetUploadDetailsRpcPaymentsAccepts();
// Submitting the form should show the upload save bubble and legal footer.
// (Must wait for response from Payments before accessing the controller.)
ResetEventWaiterForSequence(
{DialogEvent::REQUESTED_UPLOAD_SAVE,
DialogEvent::RECEIVED_GET_UPLOAD_DETAILS_RESPONSE});
NavigateTo(kCreditCardUploadForm);
FillAndSubmitForm();
WaitForObservedEvent();
EXPECT_TRUE(
FindViewInBubbleById(DialogViewId::MAIN_CONTENT_VIEW_UPLOAD)->visible());
EXPECT_TRUE(FindViewInBubbleById(DialogViewId::FOOTNOTE_VIEW)->visible());
base::HistogramTester histogram_tester;
ClickOnCancelButton();
// Ensure that no strike was added because the feature is disabled.
histogram_tester.ExpectTotalCount(
"Autofill.StrikeDatabase.NthStrikeAdded.CreditCardSave", 0);
}
// Tests StrikeDatabase interaction with the local save bubble. Ensures that a
// strike is added if the bubble is ignored.
IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
StrikeDatabase_Local_AddStrikeIfBubbleIgnored) {
scoped_feature_list_.InitAndEnableFeature(
features::kAutofillSaveCreditCardUsesStrikeSystemV2);
TestAutofillClock test_clock;
test_clock.SetNow(base::Time::Now());
// Set up the Payments RPC.
SetUploadDetailsRpcPaymentsDeclines();
// Submitting the form and having Payments decline offering to save should
// show the local save bubble.
// (Must wait for response from Payments before accessing the controller.)
ResetEventWaiterForSequence({DialogEvent::OFFERED_LOCAL_SAVE});
NavigateTo(kCreditCardUploadForm);
FillAndSubmitForm();
WaitForObservedEvent();
EXPECT_TRUE(
FindViewInBubbleById(DialogViewId::MAIN_CONTENT_VIEW_LOCAL)->visible());
// Clicking the [X] close button should dismiss the bubble.
ClickOnCloseButton();
// Add an event observer to the controller to detect strike changes.
AddEventObserverToController();
base::HistogramTester histogram_tester;
// Wait long enough to avoid bubble stickiness, then navigate away from the
// page.
test_clock.Advance(kCardBubbleSurviveNavigationTime);
ResetEventWaiterForSequence({DialogEvent::STRIKE_CHANGE_COMPLETE});
NavigateTo(kCreditCardUploadForm);
WaitForObservedEvent();
// Ensure that a strike was added due to the bubble being ignored.
histogram_tester.ExpectUniqueSample(
"Autofill.StrikeDatabase.NthStrikeAdded.CreditCardSave",
/*sample=*/1, /*count=*/1);
}
// Tests StrikeDatabase interaction with the upload save bubble. Ensures that a
// strike is added if the bubble is ignored.
IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
StrikeDatabase_Upload_AddStrikeIfBubbleIgnored) {
scoped_feature_list_.InitWithFeatures(
// Enabled
{features::kAutofillUpstream,
features::kAutofillSaveCreditCardUsesStrikeSystemV2},
// Disabled
{});
TestAutofillClock test_clock;
test_clock.SetNow(base::Time::Now());
// Start sync.
harness_->SetupSync();
// Set up the Payments RPC.
SetUploadDetailsRpcPaymentsAccepts();
// Submitting the form should show the upload save bubble and legal footer.
// (Must wait for response from Payments before accessing the controller.)
ResetEventWaiterForSequence(
{DialogEvent::REQUESTED_UPLOAD_SAVE,
DialogEvent::RECEIVED_GET_UPLOAD_DETAILS_RESPONSE});
NavigateTo(kCreditCardUploadForm);
FillAndSubmitForm();
WaitForObservedEvent();
EXPECT_TRUE(
FindViewInBubbleById(DialogViewId::MAIN_CONTENT_VIEW_UPLOAD)->visible());
EXPECT_TRUE(FindViewInBubbleById(DialogViewId::FOOTNOTE_VIEW)->visible());
// Clicking the [X] close button should dismiss the bubble.
ClickOnCloseButton();
// Add an event observer to the controller to detect strike changes.
AddEventObserverToController();
base::HistogramTester histogram_tester;
// Wait long enough to avoid bubble stickiness, then navigate away from the
// page.
test_clock.Advance(kCardBubbleSurviveNavigationTime);
ResetEventWaiterForSequence({DialogEvent::STRIKE_CHANGE_COMPLETE});
NavigateTo(kCreditCardUploadForm);
WaitForObservedEvent();
// Ensure that a strike was added due to the bubble being ignored.
histogram_tester.ExpectUniqueSample(
"Autofill.StrikeDatabase.NthStrikeAdded.CreditCardSave",
/*sample=*/1, /*count=*/1);
}
// Tests the local save bubble. Ensures that clicking the [No thanks] button
// successfully causes a strike to be added.
IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
StrikeDatabase_Local_AddStrikeIfBubbleDeclined) {
// Enable the updated UI.
scoped_feature_list_.InitWithFeatures(
// Enabled
{features::kAutofillSaveCardImprovedUserConsent,
features::kAutofillSaveCreditCardUsesStrikeSystemV2},
// Disabled
{});
// Submitting the form and having Payments decline offering to save should
// show the local save bubble.
// (Must wait for response from Payments before accessing the controller.)
ResetEventWaiterForSequence({DialogEvent::OFFERED_LOCAL_SAVE});
NavigateTo(kCreditCardUploadForm);
FillAndSubmitForm();
WaitForObservedEvent();
EXPECT_TRUE(
FindViewInBubbleById(DialogViewId::MAIN_CONTENT_VIEW_LOCAL)->visible());
// Clicking [No thanks] should cancel and close it.
base::HistogramTester histogram_tester;
ClickOnCancelButton(/*strike_expected=*/true);
// Ensure that a strike was added.
histogram_tester.ExpectUniqueSample(
"Autofill.StrikeDatabase.NthStrikeAdded.CreditCardSave",
/*sample=*/(1), /*count=*/1);
}
// Tests the upload save bubble. Ensures that clicking the [No thanks] button
// successfully causes a strike to be added.
IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
StrikeDatabase_Upload_AddStrikeIfBubbleDeclined) {
// Enable the updated UI.
scoped_feature_list_.InitWithFeatures(
// Enabled
{features::kAutofillSaveCardImprovedUserConsent,
features::kAutofillUpstream,
features::kAutofillSaveCreditCardUsesStrikeSystemV2},
// Disabled
{});
// Start sync.
harness_->SetupSync();
// Set up the Payments RPC.
SetUploadDetailsRpcPaymentsAccepts();
// Submitting the form should show the upload save bubble and legal footer.
// (Must wait for response from Payments before accessing the controller.)
ResetEventWaiterForSequence(
{DialogEvent::REQUESTED_UPLOAD_SAVE,
DialogEvent::RECEIVED_GET_UPLOAD_DETAILS_RESPONSE});
NavigateTo(kCreditCardUploadForm);
FillAndSubmitForm();
WaitForObservedEvent();
EXPECT_TRUE(
FindViewInBubbleById(DialogViewId::MAIN_CONTENT_VIEW_UPLOAD)->visible());
EXPECT_TRUE(FindViewInBubbleById(DialogViewId::FOOTNOTE_VIEW)->visible());
// Clicking [No thanks] should cancel and close it.
base::HistogramTester histogram_tester;
ClickOnCancelButton(/*strike_expected=*/true);
// Ensure that a strike was added.
histogram_tester.ExpectUniqueSample(
"Autofill.StrikeDatabase.NthStrikeAdded.CreditCardSave",
/*sample=*/(1), /*count=*/1);
}
// Tests overall StrikeDatabase interaction with the local save bubble. Runs an
// example of declining the prompt three times and ensuring that the
// offer-to-save bubble does not appear on the fourth try. Then, ensures that no
// strikes are added if the card already has max strikes.
IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
StrikeDatabase_Local_FullFlowTest) {
scoped_feature_list_.InitWithFeatures(
// Enabled
{features::kAutofillSaveCardImprovedUserConsent,
features::kAutofillSaveCreditCardUsesStrikeSystemV2},
// Disabled
{});
bool controller_observer_set = false;
// Show and ignore the bubble kMaxStrikesToPreventPoppingUpOfferToSavePrompt
// times in order to accrue maximum strikes.
for (int i = 0; i < kMaxStrikesToPreventPoppingUpOfferToSavePrompt; i++) {
// Submitting the form and having Payments decline offering to save should
// show the local save bubble.
// (Must wait for response from Payments before accessing the controller.)
ResetEventWaiterForSequence({DialogEvent::OFFERED_LOCAL_SAVE});
NavigateTo(kCreditCardUploadForm);
FillAndSubmitForm();
WaitForObservedEvent();
EXPECT_TRUE(
FindViewInBubbleById(DialogViewId::MAIN_CONTENT_VIEW_LOCAL)->visible());
if (!controller_observer_set) {
// Add an event observer to the controller.
AddEventObserverToController();
ReduceAnimationTime();
controller_observer_set = true;
}
base::HistogramTester histogram_tester;
ResetEventWaiterForSequence({DialogEvent::STRIKE_CHANGE_COMPLETE});
ClickOnCancelButton(/*strike_expected=*/true);
WaitForObservedEvent();
// Ensure that a strike was added due to the bubble being declined.
// The sample logged is the Nth strike added, or (i+1).
histogram_tester.ExpectUniqueSample(
"Autofill.StrikeDatabase.NthStrikeAdded.CreditCardSave",
/*sample=*/(i + 1), /*count=*/1);
}
base::HistogramTester histogram_tester;
// Submit the form a fourth time. Since the card now has maximum strikes (3),
// the icon should be shown but the bubble should not.
ResetEventWaiterForSequence({DialogEvent::OFFERED_LOCAL_SAVE});
NavigateTo(kCreditCardUploadForm);
FillAndSubmitForm();
WaitForObservedEvent();
EXPECT_TRUE(GetSaveCardIconView()->visible());
EXPECT_FALSE(GetSaveCardBubbleViews());
// Click the icon to show the bubble.
ResetEventWaiterForSequence({DialogEvent::BUBBLE_SHOWN});
ClickOnView(GetSaveCardIconView());
WaitForObservedEvent();
EXPECT_TRUE(
FindViewInBubbleById(DialogViewId::MAIN_CONTENT_VIEW_LOCAL)->visible());
ClickOnCancelButton();
// Ensure that no strike was added because the card already had max strikes.
histogram_tester.ExpectTotalCount(
"Autofill.StrikeDatabase.NthStrikeAdded.CreditCardSave", 0);
// Verify that the correct histogram entry was logged.
histogram_tester.ExpectBucketCount(
"Autofill.StrikeDatabase.CreditCardSaveNotOfferedDueToMaxStrikes",
AutofillMetrics::SaveTypeMetric::LOCAL, 1);
// UMA should have recorded bubble rejection.
histogram_tester.ExpectUniqueSample(
"Autofill.SaveCreditCardPrompt.Local.FirstShow",
AutofillMetrics::SAVE_CARD_ICON_SHOWN_WITHOUT_PROMPT, 1);
}
// Tests overall StrikeDatabase interaction with the upload save bubble. Runs an
// example of declining the prompt three times and ensuring that the
// offer-to-save bubble does not appear on the fourth try. Then, ensures that no
// strikes are added if the card already has max strikes.
IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
StrikeDatabase_Upload_FullFlowTest) {
scoped_feature_list_.InitWithFeatures(
// Enabled
{features::kAutofillSaveCardImprovedUserConsent,
features::kAutofillUpstream,
features::kAutofillSaveCreditCardUsesStrikeSystemV2},
// Disabled
{});
bool controller_observer_set = false;
// Start sync.
harness_->SetupSync();
// Set up the Payments RPC.
SetUploadDetailsRpcPaymentsAccepts();
// Show and ignore the bubble kMaxStrikesToPreventPoppingUpOfferToSavePrompt
// times in order to accrue maximum strikes.
for (int i = 0; i < kMaxStrikesToPreventPoppingUpOfferToSavePrompt; i++) {
// Submitting the form should show the upload save bubble and legal footer.
// (Must wait for response from Payments before accessing the controller.)
ResetEventWaiterForSequence(
{DialogEvent::REQUESTED_UPLOAD_SAVE,
DialogEvent::RECEIVED_GET_UPLOAD_DETAILS_RESPONSE});
NavigateTo(kCreditCardUploadForm);
FillAndSubmitForm();
WaitForObservedEvent();
EXPECT_TRUE(FindViewInBubbleById(DialogViewId::MAIN_CONTENT_VIEW_UPLOAD)
->visible());
EXPECT_TRUE(FindViewInBubbleById(DialogViewId::FOOTNOTE_VIEW)->visible());
if (!controller_observer_set) {
// Add an event observer to the controller.
AddEventObserverToController();
ReduceAnimationTime();
controller_observer_set = true;
}
base::HistogramTester histogram_tester;
ClickOnCancelButton(/*strike_expected=*/true);
WaitForObservedEvent();
// Ensure that a strike was added due to the bubble being declined.
// The sample logged is the Nth strike added, or (i+1).
histogram_tester.ExpectUniqueSample(
"Autofill.StrikeDatabase.NthStrikeAdded.CreditCardSave",
/*sample=*/(i + 1), /*count=*/1);
}
base::HistogramTester histogram_tester;
// Submit the form a fourth time. Since the card now has maximum strikes (3),
// the icon should be shown but the bubble should not.
ResetEventWaiterForSequence(
{DialogEvent::REQUESTED_UPLOAD_SAVE,
DialogEvent::RECEIVED_GET_UPLOAD_DETAILS_RESPONSE});
NavigateTo(kCreditCardUploadForm);
FillAndSubmitForm();
WaitForObservedEvent();
EXPECT_TRUE(GetSaveCardIconView()->visible());
EXPECT_FALSE(GetSaveCardBubbleViews());
// Click the icon to show the bubble.
ResetEventWaiterForSequence({DialogEvent::BUBBLE_SHOWN});
ClickOnView(GetSaveCardIconView());
WaitForObservedEvent();
EXPECT_TRUE(
FindViewInBubbleById(DialogViewId::MAIN_CONTENT_VIEW_UPLOAD)->visible());
EXPECT_TRUE(FindViewInBubbleById(DialogViewId::FOOTNOTE_VIEW)->visible());
ClickOnCancelButton();
// Ensure that no strike was added because the card already had max strikes.
histogram_tester.ExpectTotalCount(
"Autofill.StrikeDatabase.NthStrikeAdded.CreditCardSave", 0);
// Verify that the correct histogram entry was logged.
histogram_tester.ExpectBucketCount(
"Autofill.StrikeDatabase.CreditCardSaveNotOfferedDueToMaxStrikes",
AutofillMetrics::SaveTypeMetric::SERVER, 1);
// UMA should have recorded bubble rejection.
histogram_tester.ExpectUniqueSample(
"Autofill.SaveCreditCardPrompt.Upload.FirstShow",
AutofillMetrics::SAVE_CARD_ICON_SHOWN_WITHOUT_PROMPT, 1);
}
} // namespace autofill