blob: c6650c7eeedade11d62bc3bc42b19c11811bf66f [file] [log] [blame]
// Copyright 2021 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 "chrome/browser/ui/views/autofill/payments/offer_notification_bubble_views_test_base.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/metrics/histogram_tester.h"
#include "build/build_config.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/page_action/page_action_icon_type.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/views/autofill/payments/promo_code_label_button.h"
#include "chrome/common/webui_url_constants.h"
#include "chrome/test/base/interactive_test_utils.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/autofill/core/browser/data_model/autofill_offer_data.h"
#include "components/autofill/core/browser/metrics/autofill_metrics.h"
#include "components/autofill/core/browser/metrics/payments/offers_metrics.h"
#include "components/autofill/core/browser/payments/offer_notification_handler.h"
#include "components/autofill/core/browser/ui/payments/payments_bubble_closed_reasons.h"
#include "components/autofill/core/common/autofill_payments_features.h"
#include "components/strings/grit/components_strings.h"
#include "content/public/test/browser_test.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/clipboard/clipboard.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/test/ui_controls.h"
#include "ui/base/window_open_disposition.h"
#include "ui/events/base_event_utils.h"
#include "ui/views/bubble/bubble_frame_view.h"
#include "ui/views/test/widget_test.h"
#include "ui/views/widget/widget.h"
namespace autofill {
typedef std::tuple<AutofillOfferData::OfferType>
OfferNotificationBubbleViewsInteractiveUiTestData;
class OfferNotificationBubbleViewsInteractiveUiTest
: public OfferNotificationBubbleViewsTestBase,
public testing::WithParamInterface<
OfferNotificationBubbleViewsInteractiveUiTestData> {
public:
OfferNotificationBubbleViewsInteractiveUiTest()
: test_offer_type_(std::get<0>(GetParam())) {}
~OfferNotificationBubbleViewsInteractiveUiTest() override = default;
OfferNotificationBubbleViewsInteractiveUiTest(
const OfferNotificationBubbleViewsInteractiveUiTest&) = delete;
OfferNotificationBubbleViewsInteractiveUiTest& operator=(
const OfferNotificationBubbleViewsInteractiveUiTest&) = delete;
void ShowBubbleForOfferAndVerify() {
switch (test_offer_type_) {
case AutofillOfferData::OfferType::GPAY_CARD_LINKED_OFFER:
ShowBubbleForCardLinkedOfferAndVerify();
break;
case AutofillOfferData::OfferType::FREE_LISTING_COUPON_OFFER:
ShowBubbleForFreeListingCouponOfferAndVerify();
break;
case AutofillOfferData::OfferType::GPAY_PROMO_CODE_OFFER:
ShowBubbleForGPayPromoCodeOfferAndVerify();
break;
case AutofillOfferData::OfferType::UNKNOWN:
NOTREACHED();
break;
}
}
void ShowBubbleForCardLinkedOfferAndVerify() {
NavigateTo(chrome::kChromeUINewTabPageURL);
// Set the initial origin that the bubble will be displayed on.
SetUpCardLinkedOfferDataWithDomains(
{GURL("https://www.merchantsite1.com/"),
GURL("https://www.merchantsite2.com/")});
ResetEventWaiterForSequence({DialogEvent::BUBBLE_SHOWN});
NavigateTo("https://www.merchantsite1.com/first");
WaitForObservedEvent();
EXPECT_TRUE(IsIconVisible());
EXPECT_TRUE(GetOfferNotificationBubbleViews());
}
void ShowBubbleForFreeListingCouponOfferAndVerify() {
NavigateTo(chrome::kChromeUINewTabPageURL);
// Set the initial origin that the bubble will be displayed on.
SetUpFreeListingCouponOfferDataWithDomains(
{GURL("https://www.merchantsite1.com/"),
GURL("https://www.merchantsite2.com/")});
ResetEventWaiterForSequence({DialogEvent::BUBBLE_SHOWN});
NavigateTo("https://www.merchantsite1.com/first");
WaitForObservedEvent();
EXPECT_TRUE(IsIconVisible());
EXPECT_TRUE(GetOfferNotificationBubbleViews());
}
void ShowBubbleForGPayPromoCodeOfferAndVerify() {
NavigateTo(chrome::kChromeUINewTabPageURL);
// Set the initial origin that the bubble will be displayed on.
SetUpGPayPromoCodeOfferDataWithDomains(
{GURL("https://www.merchantsite1.com/"),
GURL("https://www.merchantsite2.com/")});
ResetEventWaiterForSequence({DialogEvent::BUBBLE_SHOWN});
NavigateTo("https://www.merchantsite1.com/first");
WaitForObservedEvent();
EXPECT_TRUE(IsIconVisible());
EXPECT_TRUE(GetOfferNotificationBubbleViews());
}
void CloseBubbleWithReason(views::Widget::ClosedReason closed_reason) {
auto* widget = GetOfferNotificationBubbleViews()->GetWidget();
EXPECT_TRUE(widget);
views::test::WidgetDestroyedWaiter destroyed_waiter(widget);
widget->CloseWithReason(closed_reason);
destroyed_waiter.Wait();
EXPECT_FALSE(GetOfferNotificationBubbleViews());
EXPECT_TRUE(IsIconVisible());
}
void SimulateClickOnIconAndReshowBubble() {
auto* icon = GetOfferNotificationIconView();
EXPECT_TRUE(icon);
ResetEventWaiterForSequence({DialogEvent::BUBBLE_SHOWN});
chrome::ExecuteCommand(browser(), IDC_OFFERS_AND_REWARDS_FOR_PAGE);
WaitForObservedEvent();
EXPECT_TRUE(IsIconVisible());
EXPECT_TRUE(GetOfferNotificationBubbleViews());
}
std::string GetSubhistogramNameForOfferType() const {
switch (test_offer_type_) {
case AutofillOfferData::OfferType::GPAY_CARD_LINKED_OFFER:
return "CardLinkedOffer";
case AutofillOfferData::OfferType::GPAY_PROMO_CODE_OFFER:
return "GPayPromoCodeOffer";
case AutofillOfferData::OfferType::FREE_LISTING_COUPON_OFFER:
return "FreeListingCouponOffer";
case AutofillOfferData::OfferType::UNKNOWN:
NOTREACHED();
return std::string();
}
}
void ClearNotificationActiveDomainsForTesting() {
GetOfferManager()
->notification_handler_.ClearShownNotificationIdForTesting();
}
const AutofillOfferData::OfferType test_offer_type_;
};
// TODO(https://crbug.com/1334806): Split parameterized tests that are
// applicable for only one offer type.
INSTANTIATE_TEST_SUITE_P(
GpayCardLinked,
OfferNotificationBubbleViewsInteractiveUiTest,
testing::Values(AutofillOfferData::OfferType::GPAY_CARD_LINKED_OFFER));
INSTANTIATE_TEST_SUITE_P(
FreeListingCoupon,
OfferNotificationBubbleViewsInteractiveUiTest,
testing::Values(AutofillOfferData::OfferType::FREE_LISTING_COUPON_OFFER));
INSTANTIATE_TEST_SUITE_P(
GPayPromoCode,
OfferNotificationBubbleViewsInteractiveUiTest,
testing::Values(AutofillOfferData::OfferType::GPAY_PROMO_CODE_OFFER));
// TODO(https://crbug.com/1289161): Flaky failures.
#if BUILDFLAG(IS_LINUX)
#define MAYBE_Navigation DISABLED_Navigation
#else
#define MAYBE_Navigation Navigation
#endif
IN_PROC_BROWSER_TEST_P(OfferNotificationBubbleViewsInteractiveUiTest,
MAYBE_Navigation) {
static const struct {
std::string url_navigated_to;
bool bubble_should_be_visible;
} test_cases[] = {
// Different page on same domain keeps bubble.
{"https://www.merchantsite1.com/second/", true},
// Different domain not in offer's list dismisses bubble.
{"https://www.about.com/", false},
// Subdomain not in offer's list dismisses bubble.
{"https://support.merchantsite1.com/first/", false},
// http vs. https mismatch dismisses bubble.
{"http://www.merchantsite1.com/first/", false},
// Different domain in the offer's list keeps bubble.
{"https://www.merchantsite2.com/first/", true},
};
// Set the initial origin that the bubble will be displayed on.
SetUpOfferDataWithDomains(test_offer_type_,
{GURL("https://www.merchantsite1.com/"),
GURL("https://www.merchantsite2.com/")});
for (const auto& test_case : test_cases) {
ClearNotificationActiveDomainsForTesting();
NavigateTo(chrome::kChromeUINewTabPageURL);
ResetEventWaiterForSequence({DialogEvent::BUBBLE_SHOWN});
NavigateTo("https://www.merchantsite1.com/first");
WaitForObservedEvent();
// Bubble should be visible.
ASSERT_TRUE(IsIconVisible());
ASSERT_TRUE(GetOfferNotificationBubbleViews());
// Navigate to a different url, and verify bubble/icon visibility.
if (test_case.bubble_should_be_visible) {
NavigateTo(test_case.url_navigated_to);
} else {
views::test::WidgetDestroyedWaiter destroyed_waiter(
GetOfferNotificationBubbleViews()->GetWidget());
NavigateTo(test_case.url_navigated_to);
destroyed_waiter.Wait();
}
EXPECT_EQ(test_case.bubble_should_be_visible, IsIconVisible());
EXPECT_EQ(test_case.bubble_should_be_visible,
!!GetOfferNotificationBubbleViews());
}
}
// Verifies the behavior of the offer notification bubble on different tabs.
// The steps are:
// 1. Creates the offer with two applicable merchant sites
// 2. Creates the setup that foreground tab is a blank website, the first
// background tab is merchant site 1, the second background tab is merchant
// site 2.
// 3. Checks that on the current blank site the offer notification bubble will
// not be shown.
// 4. Switches to the tab of merchant site 1. Makes sure the bubble and the icon
// are both visible.
// 5. Switches to the blank site. Makes sure the bubble and icon will be gone.
// 6. Switches to merchant site 2. Makes sure the icon is visible but the bubble
// is not, since we have shown the offer bubble in the tab of merchant site 1.
IN_PROC_BROWSER_TEST_P(OfferNotificationBubbleViewsInteractiveUiTest,
CrossTabTracking) {
SetUpOfferDataWithDomains(test_offer_type_,
{GURL("https://www.merchantsite1.com/"),
GURL("https://www.merchantsite2.com/")});
// Makes sure the foreground tab is a blank site.
NavigateTo("about:blank");
// Creates first background tab.
ui_test_utils::NavigateToURLWithDisposition(
browser(), GURL("https://www.merchantsite1.com/"),
WindowOpenDisposition::NEW_BACKGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
OfferNotificationBubbleControllerImpl* controller =
static_cast<OfferNotificationBubbleControllerImpl*>(
OfferNotificationBubbleController::GetOrCreate(
browser()->tab_strip_model()->GetWebContentsAt(1)));
ASSERT_TRUE(controller);
AddEventObserverToController(controller);
// Creates another merchant website in a second background tab.
ui_test_utils::NavigateToURLWithDisposition(
browser(), GURL("https://www.merchantsite2.com/"),
WindowOpenDisposition::NEW_BACKGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
controller = static_cast<OfferNotificationBubbleControllerImpl*>(
OfferNotificationBubbleController::GetOrCreate(
browser()->tab_strip_model()->GetWebContentsAt(2)));
ASSERT_TRUE(controller);
AddEventObserverToController(controller);
// On current page notification should not be active.
EXPECT_FALSE(IsIconVisible());
EXPECT_FALSE(GetOfferNotificationBubbleViews());
// Change to the first background tab.
ResetEventWaiterForSequence({DialogEvent::BUBBLE_SHOWN});
browser()->tab_strip_model()->ActivateTabAt(1);
WaitForObservedEvent();
// Icon should always be visible, and the bubble should be visible too.
EXPECT_TRUE(IsIconVisible());
ASSERT_TRUE(GetOfferNotificationBubbleViews());
// Change back to the "original tab". The destroyed_waiter will wail until the
// bubble is successfully dismissed and destroyed before proceeding with the
// checks.
views::test::WidgetDestroyedWaiter destroyed_waiter(
GetOfferNotificationBubbleViews()->GetWidget());
browser()->tab_strip_model()->ActivateTabAt(0);
destroyed_waiter.Wait();
// The icon and the bubble should not be visible.
EXPECT_FALSE(IsIconVisible());
EXPECT_FALSE(GetOfferNotificationBubbleViews());
// Change to the second background tab.
browser()->tab_strip_model()->ActivateTabAt(2);
// Icon should be visible and the bubble should not be visible.
EXPECT_TRUE(IsIconVisible());
EXPECT_FALSE(GetOfferNotificationBubbleViews());
}
// Tests that bubble behaves correctly after user dismisses it.
IN_PROC_BROWSER_TEST_P(OfferNotificationBubbleViewsInteractiveUiTest,
DismissBubble) {
// Applies to card-linked offers only, as promo code offers do not have an OK
// button.
if (test_offer_type_ != AutofillOfferData::OfferType::GPAY_CARD_LINKED_OFFER)
return;
ShowBubbleForOfferAndVerify();
// Dismiss the bubble by clicking the ok button.
CloseBubbleWithReason(views::Widget::ClosedReason::kAcceptButtonClicked);
// Navigates to another valid domain will not reshow the bubble.
NavigateTo("https://www.merchantsite1.com/second");
EXPECT_FALSE(GetOfferNotificationBubbleViews());
EXPECT_TRUE(IsIconVisible());
// Navigates to an invalid domain will dismiss the icon.
NavigateTo("https://www.about.com/");
EXPECT_FALSE(GetOfferNotificationBubbleViews());
EXPECT_FALSE(IsIconVisible());
}
IN_PROC_BROWSER_TEST_P(OfferNotificationBubbleViewsInteractiveUiTest,
Logging_Shown) {
base::HistogramTester histogram_tester;
ShowBubbleForOfferAndVerify();
histogram_tester.ExpectBucketCount("Autofill.OfferNotificationBubbleOffer." +
GetSubhistogramNameForOfferType(),
/*firstshow*/ false, 1);
// Dismiss the bubble by clicking the close button.
CloseBubbleWithReason(views::Widget::ClosedReason::kCloseButtonClicked);
// Click on the omnibox icon to reshow the bubble.
SimulateClickOnIconAndReshowBubble();
histogram_tester.ExpectBucketCount("Autofill.OfferNotificationBubbleOffer." +
GetSubhistogramNameForOfferType(),
/*reshow*/ true, 1);
}
IN_PROC_BROWSER_TEST_P(OfferNotificationBubbleViewsInteractiveUiTest,
Logging_Acknowledged) {
// Applies to card-linked offers only, as promo code offers do not have an OK
// button.
if (test_offer_type_ != AutofillOfferData::OfferType::GPAY_CARD_LINKED_OFFER)
return;
base::HistogramTester histogram_tester;
ShowBubbleForOfferAndVerify();
// Dismiss the bubble by clicking the ok button.
CloseBubbleWithReason(views::Widget::ClosedReason::kAcceptButtonClicked);
histogram_tester.ExpectUniqueSample(
"Autofill.OfferNotificationBubbleResult." +
GetSubhistogramNameForOfferType() + ".FirstShow",
autofill_metrics::OfferNotificationBubbleResultMetric::
OFFER_NOTIFICATION_BUBBLE_ACKNOWLEDGED,
1);
// Click on the omnibox icon to reshow the bubble.
SimulateClickOnIconAndReshowBubble();
// Click on the ok button to dismiss the bubble.
CloseBubbleWithReason(views::Widget::ClosedReason::kAcceptButtonClicked);
histogram_tester.ExpectUniqueSample(
"Autofill.OfferNotificationBubbleResult." +
GetSubhistogramNameForOfferType() + ".Reshows",
autofill_metrics::OfferNotificationBubbleResultMetric::
OFFER_NOTIFICATION_BUBBLE_ACKNOWLEDGED,
1);
}
IN_PROC_BROWSER_TEST_P(OfferNotificationBubbleViewsInteractiveUiTest,
Logging_Closed) {
base::HistogramTester histogram_tester;
ShowBubbleForOfferAndVerify();
// Dismiss the bubble by clicking the close button.
CloseBubbleWithReason(views::Widget::ClosedReason::kCloseButtonClicked);
histogram_tester.ExpectUniqueSample(
"Autofill.OfferNotificationBubbleResult." +
GetSubhistogramNameForOfferType() + ".FirstShow",
autofill_metrics::OfferNotificationBubbleResultMetric::
OFFER_NOTIFICATION_BUBBLE_CLOSED,
1);
// Click on the omnibox icon to reshow the bubble.
SimulateClickOnIconAndReshowBubble();
// Click on the close button to dismiss the bubble.
CloseBubbleWithReason(views::Widget::ClosedReason::kCloseButtonClicked);
histogram_tester.ExpectUniqueSample(
"Autofill.OfferNotificationBubbleResult." +
GetSubhistogramNameForOfferType() + ".Reshows",
autofill_metrics::OfferNotificationBubbleResultMetric::
OFFER_NOTIFICATION_BUBBLE_CLOSED,
1);
}
IN_PROC_BROWSER_TEST_P(OfferNotificationBubbleViewsInteractiveUiTest,
Logging_NotInteracted) {
base::HistogramTester histogram_tester;
ShowBubbleForOfferAndVerify();
// Mock browser being closed.
views::test::WidgetDestroyedWaiter destroyed_waiter(
GetOfferNotificationBubbleViews()->GetWidget());
browser()->tab_strip_model()->CloseAllTabs();
destroyed_waiter.Wait();
histogram_tester.ExpectUniqueSample(
"Autofill.OfferNotificationBubbleResult." +
GetSubhistogramNameForOfferType() + ".FirstShow",
autofill_metrics::OfferNotificationBubbleResultMetric::
OFFER_NOTIFICATION_BUBBLE_NOT_INTERACTED,
1);
}
IN_PROC_BROWSER_TEST_P(OfferNotificationBubbleViewsInteractiveUiTest,
Logging_LostFocus) {
base::HistogramTester histogram_tester;
ShowBubbleForOfferAndVerify();
// Mock deactivation due to lost focus.
CloseBubbleWithReason(views::Widget::ClosedReason::kLostFocus);
histogram_tester.ExpectUniqueSample(
"Autofill.OfferNotificationBubbleResult." +
GetSubhistogramNameForOfferType() + ".FirstShow",
autofill_metrics::OfferNotificationBubbleResultMetric::
OFFER_NOTIFICATION_BUBBLE_LOST_FOCUS,
1);
// Click on the omnibox icon to reshow the bubble.
SimulateClickOnIconAndReshowBubble();
// Mock deactivation due to lost focus.
CloseBubbleWithReason(views::Widget::ClosedReason::kLostFocus);
histogram_tester.ExpectUniqueSample(
"Autofill.OfferNotificationBubbleResult." +
GetSubhistogramNameForOfferType() + ".Reshows",
autofill_metrics::OfferNotificationBubbleResultMetric::
OFFER_NOTIFICATION_BUBBLE_LOST_FOCUS,
1);
}
IN_PROC_BROWSER_TEST_P(OfferNotificationBubbleViewsInteractiveUiTest,
TooltipAndAccessibleName) {
// Applies to free listing coupons offers only, as other offers do not have a
// clickable promo code copy button.
if (test_offer_type_ !=
AutofillOfferData::OfferType::FREE_LISTING_COUPON_OFFER) {
return;
}
ShowBubbleForOfferAndVerify();
ASSERT_TRUE(GetOfferNotificationBubbleViews());
ASSERT_TRUE(IsIconVisible());
std::u16string normal_button_tooltip = l10n_util::GetStringUTF16(
IDS_AUTOFILL_PROMO_CODE_OFFER_BUTTON_TOOLTIP_NORMAL);
std::u16string clicked_button_tooltip = l10n_util::GetStringUTF16(
IDS_AUTOFILL_PROMO_CODE_OFFER_BUTTON_TOOLTIP_CLICKED);
auto* promo_code_label_button =
GetOfferNotificationBubbleViews()->promo_code_label_button_.get();
EXPECT_EQ(normal_button_tooltip, promo_code_label_button->GetTooltipText());
EXPECT_EQ(promo_code_label_button->GetText() + u" " + normal_button_tooltip,
promo_code_label_button->GetAccessibleName());
GetOfferNotificationBubbleViews()->OnPromoCodeButtonClicked();
EXPECT_EQ(clicked_button_tooltip, promo_code_label_button->GetTooltipText());
EXPECT_EQ(promo_code_label_button->GetText() + u" " + clicked_button_tooltip,
promo_code_label_button->GetAccessibleName());
}
IN_PROC_BROWSER_TEST_P(OfferNotificationBubbleViewsInteractiveUiTest,
CopyPromoCode) {
// Applies to free listing coupons offers only, as other offers do not have a
// clickable promo code copy button.
if (test_offer_type_ !=
AutofillOfferData::OfferType::FREE_LISTING_COUPON_OFFER) {
return;
}
ShowBubbleForOfferAndVerify();
ASSERT_TRUE(GetOfferNotificationBubbleViews());
ASSERT_TRUE(IsIconVisible());
// Simulate clicking on the copy promo code button.
base::HistogramTester histogram_tester;
GetOfferNotificationBubbleViews()->OnPromoCodeButtonClicked();
// Clipboard should have the promo code text, which should be the same as what
// is on the promo code button, and it should have logged the click.
ui::Clipboard* clipboard = ui::Clipboard::GetForCurrentThread();
std::u16string clipboard_text;
clipboard->ReadText(ui::ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr,
&clipboard_text);
std::u16string test_promo_code =
base::ASCIIToUTF16(GetDefaultTestPromoCode());
EXPECT_EQ(clipboard_text, test_promo_code);
EXPECT_EQ(
GetOfferNotificationBubbleViews()->promo_code_label_button_->GetText(),
test_promo_code);
histogram_tester.ExpectBucketCount(
"Autofill.OfferNotificationBubblePromoCodeButtonClicked." +
GetSubhistogramNameForOfferType(),
true, 1);
}
IN_PROC_BROWSER_TEST_P(OfferNotificationBubbleViewsInteractiveUiTest,
ShowGPayPromoCodeBubble) {
// Applies to GPay promo code offers only.
if (test_offer_type_ != AutofillOfferData::OfferType::GPAY_PROMO_CODE_OFFER) {
return;
}
ShowBubbleForOfferAndVerify();
ASSERT_TRUE(GetOfferNotificationBubbleViews());
ASSERT_TRUE(IsIconVisible());
auto* promo_code_styled_label =
GetOfferNotificationBubbleViews()->promo_code_label_.get();
auto* promo_code_usage_instructions_ =
GetOfferNotificationBubbleViews()->instructions_label_.get();
EXPECT_EQ(promo_code_styled_label->GetText(),
base::ASCIIToUTF16(GetDefaultTestValuePropText()) + u" " +
base::ASCIIToUTF16(GetDefaultTestSeeDetailsText()));
EXPECT_EQ(promo_code_usage_instructions_->GetText(),
base::ASCIIToUTF16(GetDefaultTestUsageInstructionsText()));
// Simulate clicking on see details part of the text.
GetOfferNotificationBubbleViews()->OnPromoCodeSeeDetailsClicked();
EXPECT_EQ(
browser()->tab_strip_model()->GetActiveWebContents()->GetVisibleURL(),
GURL(GetDefaultTestDetailsUrlString()));
}
} // namespace autofill