Commit 55e420f0 authored by Collin Baker's avatar Collin Baker Committed by Commit Bot

Add integration tests for IPH snooze

This adds interactive UI tests for all ways of terminating a
snoozeable promo:
* User clicking the snooze button
* User clicking the dismiss button
* Closing the promo through FeaturePromoController
* Directly closing the promo's widget (as if the user pressed ESC)

The tests verify the snooze profile prefs are set correctly after
these operations.

Bug: 1121399
Change-Id: I5ab462d51abc771edc0d27afb5093d756e7e3ccd
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2441115
Auto-Submit: Collin Baker <collinbaker@chromium.org>
Commit-Queue: Wei Li <weili@chromium.org>
Reviewed-by: default avatarWei Li <weili@chromium.org>
Reviewed-by: default avatarHenrique Nakashima <hnakashima@chromium.org>
Cr-Commit-Position: refs/heads/master@{#812774}
parent 2efc65c1
......@@ -6,6 +6,7 @@
#define CHROME_BROWSER_UI_IN_PRODUCT_HELP_FEATURE_PROMO_SNOOZE_SERVICE_H_
#include <string>
#include "base/optional.h"
#include "base/time/time.h"
......@@ -61,6 +62,10 @@ class FeaturePromoSnoozeService {
static void RegisterProfilePrefs(PrefRegistrySimple* registry);
private:
// TODO(crbug.com/1121399): refactor prefs code so friending tests
// isn't necessary.
friend class FeaturePromoSnoozeInteractiveTest;
// Snooze information dictionary saved under path
// in_product_help.snoozed_feature.[iph_name] in PerfService.
struct SnoozeData {
......
// Copyright 2020 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 "base/optional.h"
#include "base/test/scoped_feature_list.h"
#include "base/time/time.h"
#include "chrome/browser/feature_engagement/tracker_factory.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/in_product_help/feature_promo_snooze_service.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/in_product_help/feature_promo_bubble_view.h"
#include "chrome/browser/ui/views/in_product_help/feature_promo_controller_views.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/interactive_test_utils.h"
#include "components/feature_engagement/public/feature_constants.h"
#include "components/feature_engagement/test/mock_tracker.h"
#include "components/keyed_service/content/browser_context_dependency_manager.h"
#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
#include "content/public/test/browser_test.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/test/ui_controls.h"
#include "ui/views/controls/button/button.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"
#include "url/gurl.h"
using ::testing::_;
using ::testing::AnyNumber;
using ::testing::NiceMock;
using ::testing::Ref;
using ::testing::Return;
class FeaturePromoSnoozeInteractiveTest : public InProcessBrowserTest {
public:
FeaturePromoSnoozeInteractiveTest() {
scoped_feature_list_.InitAndEnableFeature(
feature_engagement::kIPHDesktopSnoozeFeature);
subscription_ = BrowserContextDependencyManager::GetInstance()
->RegisterCreateServicesCallbackForTesting(
base::BindRepeating(RegisterMockTracker));
}
void SetUpOnMainThread() override {
mock_tracker_ =
static_cast<NiceMock<feature_engagement::test::MockTracker>*>(
feature_engagement::TrackerFactory::GetForBrowserContext(
browser()->profile()));
ASSERT_TRUE(mock_tracker_);
promo_controller_ = BrowserView::GetBrowserViewForBrowser(browser())
->feature_promo_controller();
snooze_service_ = promo_controller_->snooze_service_for_testing();
}
protected:
void ClickButton(views::Button* button) {
base::RunLoop run_loop;
ui_test_utils::MoveMouseToCenterAndPress(
button, ui_controls::LEFT, ui_controls::DOWN | ui_controls::UP,
run_loop.QuitClosure());
run_loop.Run();
}
bool HasSnoozePrefs(const base::Feature& iph_feature) {
return snooze_service_->ReadSnoozeData(iph_feature).has_value();
}
void CheckSnoozePrefs(const base::Feature& iph_feature,
bool is_dismissed,
int snooze_count,
base::Time last_snooze_time_min,
base::Time last_snooze_time_max) {
auto data = snooze_service_->ReadSnoozeData(iph_feature);
// If false, adds a failure and returns early from this function.
ASSERT_TRUE(data.has_value());
EXPECT_EQ(data->is_dismissed, is_dismissed);
EXPECT_EQ(data->snooze_count, snooze_count);
// last_snooze_time is only meaningful if a snooze has occurred.
if (data->snooze_count > 0) {
EXPECT_GE(data->last_snooze_time, last_snooze_time_min);
EXPECT_LE(data->last_snooze_time, last_snooze_time_max);
}
}
void SetSnoozePrefs(const base::Feature& iph_feature,
bool is_dismissed,
int snooze_count,
base::Time last_snooze_time,
base::TimeDelta last_snooze_duration) {
FeaturePromoSnoozeService::SnoozeData data;
data.is_dismissed = is_dismissed;
data.snooze_count = snooze_count;
data.last_snooze_time = last_snooze_time;
data.last_snooze_duration = last_snooze_duration;
snooze_service_->SaveSnoozeData(iph_feature, data);
}
// Tries to show tab groups IPH by meeting the trigger conditions. If
// |should_show| is true it checks that it was shown. If false, it
// checks that it was not shown.
void AttemptTabGroupsIPH(bool should_show) {
if (should_show) {
EXPECT_CALL(*mock_tracker_,
ShouldTriggerHelpUI(Ref(
feature_engagement::kIPHDesktopTabGroupsNewGroupFeature)))
.WillOnce(Return(true));
} else {
EXPECT_CALL(*mock_tracker_,
ShouldTriggerHelpUI(Ref(
feature_engagement::kIPHDesktopTabGroupsNewGroupFeature)))
.Times(0);
}
// Opening 6 or more tabs is the triggering event for tab groups
// IPH.
for (int i = 0; i < 5; ++i)
AddTabAtIndex(0, GURL("about:blank"), ui::PAGE_TRANSITION_TYPED);
ASSERT_EQ(should_show,
promo_controller_->BubbleIsShowing(
feature_engagement::kIPHDesktopTabGroupsNewGroupFeature));
// If shown, Tracker::Dismissed should be called eventually.
if (should_show) {
EXPECT_CALL(
*mock_tracker_,
Dismissed(
Ref(feature_engagement::kIPHDesktopTabGroupsNewGroupFeature)));
}
}
NiceMock<feature_engagement::test::MockTracker>* mock_tracker_;
FeaturePromoControllerViews* promo_controller_;
FeaturePromoSnoozeService* snooze_service_;
private:
static void RegisterMockTracker(content::BrowserContext* context) {
feature_engagement::TrackerFactory::GetInstance()->SetTestingFactory(
context, base::BindRepeating(CreateMockTracker));
}
static std::unique_ptr<KeyedService> CreateMockTracker(
content::BrowserContext* context) {
auto mock_tracker =
std::make_unique<NiceMock<feature_engagement::test::MockTracker>>();
// Allow any other IPH to call, but don't ever show them.
EXPECT_CALL(*mock_tracker, ShouldTriggerHelpUI(_))
.Times(AnyNumber())
.WillRepeatedly(Return(false));
return mock_tracker;
}
base::test::ScopedFeatureList scoped_feature_list_;
std::unique_ptr<
BrowserContextDependencyManager::CreateServicesCallbackList::Subscription>
subscription_;
};
IN_PROC_BROWSER_TEST_F(FeaturePromoSnoozeInteractiveTest,
DismissDoesNotSnooze) {
ASSERT_NO_FATAL_FAILURE(AttemptTabGroupsIPH(true));
FeaturePromoBubbleView* promo = promo_controller_->promo_bubble_for_testing();
ClickButton(promo->GetDismissButtonForTesting());
CheckSnoozePrefs(feature_engagement::kIPHDesktopTabGroupsNewGroupFeature,
true, 0, base::Time(), base::Time());
}
IN_PROC_BROWSER_TEST_F(FeaturePromoSnoozeInteractiveTest,
SnoozeSetsCorrectTime) {
ASSERT_NO_FATAL_FAILURE(AttemptTabGroupsIPH(true));
FeaturePromoBubbleView* promo = promo_controller_->promo_bubble_for_testing();
base::Time snooze_time_min = base::Time::Now();
ClickButton(promo->GetSnoozeButtonForTesting());
base::Time snooze_time_max = base::Time::Now();
CheckSnoozePrefs(feature_engagement::kIPHDesktopTabGroupsNewGroupFeature,
false, 1, snooze_time_min, snooze_time_max);
}
IN_PROC_BROWSER_TEST_F(FeaturePromoSnoozeInteractiveTest, CanReSnooze) {
// Simulate the user snoozing the IPH.
base::TimeDelta snooze_duration = base::TimeDelta::FromHours(26);
base::Time snooze_time = base::Time::Now() - snooze_duration;
SetSnoozePrefs(feature_engagement::kIPHDesktopTabGroupsNewGroupFeature, false,
1, snooze_time, snooze_duration);
ASSERT_NO_FATAL_FAILURE(AttemptTabGroupsIPH(true));
FeaturePromoBubbleView* promo = promo_controller_->promo_bubble_for_testing();
base::Time snooze_time_min = base::Time::Now();
ClickButton(promo->GetSnoozeButtonForTesting());
base::Time snooze_time_max = base::Time::Now();
CheckSnoozePrefs(feature_engagement::kIPHDesktopTabGroupsNewGroupFeature,
false, 2, snooze_time_min, snooze_time_max);
}
IN_PROC_BROWSER_TEST_F(FeaturePromoSnoozeInteractiveTest,
DoesNotShowIfDismissed) {
// Simulate the user dismissing the IPH.
SetSnoozePrefs(feature_engagement::kIPHDesktopTabGroupsNewGroupFeature, true,
0, base::Time(), base::TimeDelta());
AttemptTabGroupsIPH(false);
}
IN_PROC_BROWSER_TEST_F(FeaturePromoSnoozeInteractiveTest,
DoesNotShowBeforeSnoozeDuration) {
// Simulate a very recent snooze.
base::TimeDelta snooze_duration = base::TimeDelta::FromHours(26);
base::Time snooze_time = base::Time::Now();
SetSnoozePrefs(feature_engagement::kIPHDesktopTabGroupsNewGroupFeature, false,
1, snooze_time, snooze_duration);
AttemptTabGroupsIPH(false);
}
IN_PROC_BROWSER_TEST_F(FeaturePromoSnoozeInteractiveTest,
CloseBubbleSetsNoPrefs) {
ASSERT_NO_FATAL_FAILURE(AttemptTabGroupsIPH(true));
promo_controller_->CloseBubble(
feature_engagement::kIPHDesktopTabGroupsNewGroupFeature);
EXPECT_FALSE(
HasSnoozePrefs(feature_engagement::kIPHDesktopTabGroupsNewGroupFeature));
}
IN_PROC_BROWSER_TEST_F(FeaturePromoSnoozeInteractiveTest,
WidgetCloseSetsNoPrefs) {
ASSERT_NO_FATAL_FAILURE(AttemptTabGroupsIPH(true));
FeaturePromoBubbleView* promo = promo_controller_->promo_bubble_for_testing();
promo->GetWidget()->CloseWithReason(
views::Widget::ClosedReason::kEscKeyPressed);
EXPECT_FALSE(
HasSnoozePrefs(feature_engagement::kIPHDesktopTabGroupsNewGroupFeature));
}
......@@ -6278,6 +6278,7 @@ if (!is_android) {
"//chrome/browser/devtools:test_support",
"//chrome/browser/resource_coordinator:tab_metrics_event_proto",
"//chrome/renderer",
"//components/feature_engagement/test:test_support",
"//components/keep_alive_registry",
"//components/media_router/browser:test_support",
"//components/resources",
......@@ -6359,6 +6360,7 @@ if (!is_android) {
"../browser/ui/views/frame/browser_window_property_manager_browsertest_win.cc",
"../browser/ui/views/frame/tab_strip_region_view_interactive_uitest.cc",
"../browser/ui/views/fullscreen_control/fullscreen_control_view_interactive_uitest.cc",
"../browser/ui/views/in_product_help/feature_promo_snooze_interactive_uitest.cc",
"../browser/ui/views/keyboard_access_browsertest.cc",
"../browser/ui/views/location_bar/location_icon_view_interactive_uitest.cc",
"../browser/ui/views/location_bar/selected_keyword_view_interactive_uitest.cc",
......
......@@ -9,6 +9,7 @@
#include "base/callback_forward.h"
#include "base/callback_list.h"
#include "base/compiler_specific.h"
#include "base/macros.h"
#include "components/keyed_service/core/dependency_manager.h"
#include "components/keyed_service/core/keyed_service_export.h"
......@@ -67,7 +68,7 @@ class KEYED_SERVICE_EXPORT BrowserContextDependencyManager
// builders for the keyed services.
std::unique_ptr<CreateServicesCallbackList::Subscription>
RegisterCreateServicesCallbackForTesting(
const CreateServicesCallback& callback);
const CreateServicesCallback& callback) WARN_UNUSED_RESULT;
// Runtime assertion called as a part of GetServiceForBrowserContext() to
// check if |context| is considered stale. This will NOTREACHED() or
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment