// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <cassert>
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_switches.h"
#include "ash/system/toast/system_nudge_view.h"
#include "ash/webui/system_apps/public/system_web_app_type.h"
#include "ash/wm/tablet_mode/tablet_mode_controller_test_api.h"
#include "base/callback_list.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/strings/stringprintf.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/ash/growth/show_notification_action_performer.h"
#include "chrome/browser/feature_engagement/tracker_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/ash/system_web_apps/system_web_app_ui_utils.h"
#include "chrome/browser/ui/browser_element_identifiers.h"
#include "chrome/test/base/ash/interactive/interactive_ash_test.h"
#include "chromeos/ash/components/growth/campaigns_constants.h"
#include "chromeos/ash/components/growth/campaigns_manager.h"
#include "components/feature_engagement/public/feature_constants.h"
#include "components/feature_engagement/test/mock_tracker.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "ui/aura/env.h"
#include "ui/base/interaction/interactive_test.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/display/screen.h"
#include "ui/message_center/message_center.h"
#include "ui/message_center/public/cpp/notification.h"
namespace {
using NudgeTestVariantsParam = std::tuple</*tablet_mode=*/bool,
/*anchor_type_window_bounds=*/bool>;
constexpr char kCampaignsFileName[] = "campaigns.json";
constexpr char kEmptyCampaigns[] = R"(
{
}
)";
// Targeting Personalization App.
constexpr char kCampaignsNudgeTemplate[] = R"(
{
"2": [
{
"id": 100,
"targetings": [
{
"runtime": {
"appsOpened": [
{"appId": "glenkcidjgckcomnliblmkokolehpckn"}
]
}
}
],
"payload": {
"nudge": {
"title": "Title",
"body": "Body",
"duration": 2,
"image": {
"builtInIcon": 0
},
"arrow": %s,
"anchor": {
"activeAppWindowAnchorType": %s
},
"primaryButton": {
"label": "Yes",
"action": {
"type": 3,
"params": {
"url": "https://www.google.com",
"disposition": 0
}
},
"shouldMarkDismissed": true
},
"secondaryButton": {
"label": "No",
"action": {},
"shouldMarkDismissed": true
}
}
}
}
]
}
)";
constexpr char kCampaignsNotification[] = R"(
{
"3": [
{
"id": 101,
"targetings": [
{
"runtime": {
"triggerList": [
{
"triggerType": 1
}
]
}
}
],
"payload": {
"notification": {
"title": "Rebuy title",
"message": "Rebuy message",
"sourceIcon": {
"builtInVectorIcon": 0
},
"image": {
"builtInImage": 2
},
"shouldMarkDismissOnClose": true,
"buttons": [
{
"label": "Get Perk",
"shouldMarkDismissed": true,
"action": {
"type": 3,
"params": {
"url": "https://www.google.com",
"disposition": 0
}
}
},
{
"label": "Dismiss",
"shouldMarkDismissed": true,
"action": {
"type": 0
}
}
]
}
}
}
]
}
)";
base::FilePath GetCampaignsFilePath(const base::ScopedTempDir& dir) {
return dir.GetPath().Append(kCampaignsFileName);
}
} // namespace
// CampaignsManagerInteractiveUiTest -------------------------------------------
class CampaignsManagerInteractiveUiTest : public InteractiveAshTest {
public:
CampaignsManagerInteractiveUiTest()
: animation_duration_(
ui::ScopedAnimationDurationScaleMode::ZERO_DURATION) {
scoped_feature_list_.InitAndEnableFeature(
ash::features::kGrowthCampaignsInConsumerSession);
CHECK(temp_dir_.CreateUniqueTempDir());
base::WriteFile(GetCampaignsFilePath(temp_dir_), kEmptyCampaigns);
}
// InteractiveBrowserTest:
void SetUpCommandLine(base::CommandLine* command_line) override {
command_line->AppendSwitchNative(ash::switches::kGrowthCampaignsPath,
temp_dir_.GetPath().value());
InteractiveAshTest::SetUpCommandLine(command_line);
}
void TearDownOnMainThread() override {
if (InTabletMode()) {
ash::TabletModeControllerTestApi().LeaveTabletMode();
}
InteractiveAshTest::TearDownOnMainThread();
}
void SetUpInProcessBrowserTestFixture() override {
create_services_subscription_ =
BrowserContextDependencyManager::GetInstance()
->RegisterCreateServicesCallbackForTesting(
base::BindRepeating(&CampaignsManagerInteractiveUiTest::
OnWillCreateBrowserContextServices,
weak_ptr_factory_.GetWeakPtr()));
}
void OnWillCreateBrowserContextServices(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<
testing::NiceMock<feature_engagement::test::MockTracker>>();
ON_CALL(*mock_tracker, AddOnInitializedCallback)
.WillByDefault(
[](feature_engagement::Tracker::OnInitializedCallback callback) {
std::move(callback).Run(true);
});
ON_CALL(*mock_tracker, IsInitialized).WillByDefault(testing::Return(true));
return mock_tracker;
}
protected:
auto CheckHistogramCounts(const std::string& name,
int sample,
int expected_count) {
return Do([=, this]() {
histogram_tester_.ExpectUniqueSample(name, sample, expected_count);
});
}
auto SetTabletMode(const bool enable) {
return Do([=, this]() {
if (InTabletMode() == enable) {
return;
}
enable ? ash::TabletModeControllerTestApi().EnterTabletMode()
: ash::TabletModeControllerTestApi().LeaveTabletMode();
CHECK_EQ(InTabletMode(), enable);
});
}
auto ToggleTabletMode() { return SetTabletMode(!InTabletMode()); }
feature_engagement::test::MockTracker* GetMockTracker() {
return static_cast<feature_engagement::test::MockTracker*>(
feature_engagement::TrackerFactory::GetInstance()->GetForBrowserContext(
GetActiveUserProfile()));
}
base::ScopedTempDir temp_dir_;
private:
bool InTabletMode() { return display::Screen::GetScreen()->InTabletMode(); }
base::test::ScopedFeatureList scoped_feature_list_;
base::CallbackListSubscription create_services_subscription_;
base::HistogramTester histogram_tester_;
ui::ScopedAnimationDurationScaleMode animation_duration_;
base::WeakPtrFactory<CampaignsManagerInteractiveUiTest> weak_ptr_factory_{
this};
};
IN_PROC_BROWSER_TEST_F(CampaignsManagerInteractiveUiTest,
NotifyEventImpression) {
const std::string event_name =
"ChromeOSAshGrowthCampaigns_Campaign100_Impression";
EXPECT_CALL(*GetMockTracker(), NotifyEvent(event_name)).Times(1);
growth::CampaignsManager::Get()->RecordEventForTargeting(
growth::CampaignEvent::kImpression, "100");
}
IN_PROC_BROWSER_TEST_F(CampaignsManagerInteractiveUiTest,
NotifyEventDismissal) {
const std::string event_name =
"ChromeOSAshGrowthCampaigns_Campaign100_Dismissed";
EXPECT_CALL(*GetMockTracker(), NotifyEvent(event_name)).Times(1);
growth::CampaignsManager::Get()->RecordEventForTargeting(
growth::CampaignEvent::kDismissed, "100");
}
IN_PROC_BROWSER_TEST_F(CampaignsManagerInteractiveUiTest,
NotifyEventGroupImpression) {
const std::string event_name =
"ChromeOSAshGrowthCampaigns_Group10_Impression";
EXPECT_CALL(*GetMockTracker(), NotifyEvent(event_name)).Times(1);
growth::CampaignsManager::Get()->RecordEventForTargeting(
growth::CampaignEvent::kGroupImpression, "10");
}
IN_PROC_BROWSER_TEST_F(CampaignsManagerInteractiveUiTest,
NotifyEventGroupDismissal) {
const std::string event_name = "ChromeOSAshGrowthCampaigns_Group10_Dismissed";
EXPECT_CALL(*GetMockTracker(), NotifyEvent(event_name)).Times(1);
growth::CampaignsManager::Get()->RecordEventForTargeting(
growth::CampaignEvent::kGroupDismissed, "10");
}
IN_PROC_BROWSER_TEST_F(CampaignsManagerInteractiveUiTest,
NotifyEventAppOpened) {
const std::string event_name =
"ChromeOSAshGrowthCampaigns_AppOpened_AppId_abcd";
EXPECT_CALL(*GetMockTracker(), NotifyEvent(event_name)).Times(1);
growth::CampaignsManager::Get()->RecordEventForTargeting(
growth::CampaignEvent::kAppOpened, "abcd");
}
IN_PROC_BROWSER_TEST_F(CampaignsManagerInteractiveUiTest, ClearConfig) {
EXPECT_CALL(*GetMockTracker(), ClearEventData).Times(1);
growth::CampaignsManager::Get()->ClearEvent(growth::CampaignEvent::kAppOpened,
"abcd");
}
// CampaignsManagerInteractiveUiNudgeTest ----------------------------------
class CampaignsManagerInteractiveUiNudgeTest
: public CampaignsManagerInteractiveUiTest,
public testing::WithParamInterface<NudgeTestVariantsParam> {
public:
CampaignsManagerInteractiveUiNudgeTest() {
std::string arrow = AnchorToWindowBounds() ? "2" : "1";
std::string anchor_type = AnchorToWindowBounds() ? "1" : "0";
base::WriteFile(GetCampaignsFilePath(temp_dir_),
base::StringPrintf(kCampaignsNudgeTemplate, arrow.c_str(),
anchor_type.c_str()));
}
void SetUpOnMainThread() override {
InProcessBrowserTest::SetUpOnMainThread();
InteractiveAshTest::SetupContextWidget();
// Use SWA as targets and anchors in the tests.
InstallSystemApps();
}
protected:
auto LaunchSystemWebApp(ash::SystemWebAppType type) {
return Steps(
Do([=, this]() {
ash::LaunchSystemWebAppAsync(GetActiveUserProfile(), type);
}),
std::move(
WaitForShow(kBrowserViewElementId).SetTransitionOnlyOnEvent(true)));
}
bool ShouldUseTabletMode() { return std::get<0>(GetParam()); }
bool AnchorToWindowBounds() { return std::get<1>(GetParam()); }
};
INSTANTIATE_TEST_SUITE_P(
,
CampaignsManagerInteractiveUiNudgeTest,
testing::Combine(/*tablet_mode=*/testing::Bool(),
/*anchor_type_window_bounds=*/testing::Bool()),
[](const testing::TestParamInfo<NudgeTestVariantsParam>& info) {
return base::StrCat(
{std::get<0>(info.param) ? "TabletModeEnabled" : "TabletModeDisabled",
"_",
std::get<1>(info.param) ? "AnchorInsideWindowBounds"
: "AnchorToCaptionButtonContainer"});
});
IN_PROC_BROWSER_TEST_P(CampaignsManagerInteractiveUiNudgeTest,
AnchorPersonalizationApp) {
aura::Env* env = aura::Env::GetInstance();
ASSERT_TRUE(env);
RunTestSequence(
SetTabletMode(ShouldUseTabletMode()),
LaunchSystemWebApp(ash::SystemWebAppType::PERSONALIZATION),
WaitForWindowWithTitle(env, u"Wallpaper & style"),
WaitForShow(ash::SystemNudgeView::kBubbleIdForTesting),
WithoutDelay(Steps(
CheckHistogramCounts("Ash.Growth.Ui.Impression.Campaigns500", 100, 1),
CheckHistogramCounts(
"Ash.Growth.Ui.ButtonPressed.Button0.Campaigns500", 100, 0),
CheckHistogramCounts(
"Ash.Growth.Ui.ButtonPressed.Button1.Campaigns500", 100, 0),
CheckHistogramCounts("Ash.Growth.Ui.Dismissed.Campaigns500", 100, 0),
ToggleTabletMode())));
}
IN_PROC_BROWSER_TEST_P(CampaignsManagerInteractiveUiNudgeTest,
NotShowOnSettingsApp) {
aura::Env* env = aura::Env::GetInstance();
ASSERT_TRUE(env);
RunTestSequence(
SetTabletMode(ShouldUseTabletMode()),
LaunchSystemWebApp(ash::SystemWebAppType::SETTINGS),
WaitForWindowWithTitle(env, u"Settings"),
EnsureNotPresent(ash::SystemNudgeView::kBubbleIdForTesting),
WithoutDelay(Steps(
CheckHistogramCounts("Ash.Growth.Ui.Impression.Campaigns500", 100, 0),
CheckHistogramCounts("Ash.Growth.Ui.Dismissed.Campaigns500", 100, 0),
ToggleTabletMode())));
}
IN_PROC_BROWSER_TEST_P(CampaignsManagerInteractiveUiNudgeTest,
ClickPrimaryButtonInAnchoredNudge) {
aura::Env* env = aura::Env::GetInstance();
ASSERT_TRUE(env);
RunTestSequence(
SetTabletMode(ShouldUseTabletMode()),
LaunchSystemWebApp(ash::SystemWebAppType::PERSONALIZATION),
WaitForShow(ash::SystemNudgeView::kBubbleIdForTesting),
PressButton(ash::SystemNudgeView::kPrimaryButtonIdForTesting),
WaitForHide(ash::SystemNudgeView::kBubbleIdForTesting),
WaitForWindowWithTitle(env, u"www.google.com"),
WithoutDelay(Steps(
CheckHistogramCounts("Ash.Growth.Ui.Impression.Campaigns500", 100, 1),
CheckHistogramCounts(
"Ash.Growth.Ui.ButtonPressed.Button0.Campaigns500", 100, 1),
CheckHistogramCounts(
"Ash.Growth.Ui.ButtonPressed.Button1.Campaigns500", 100, 0),
CheckHistogramCounts("Ash.Growth.Ui.Dismissed.Campaigns500", 100,
1))));
}
IN_PROC_BROWSER_TEST_P(CampaignsManagerInteractiveUiNudgeTest,
ClickSecondaryButtonInAnchoredNudge) {
aura::Env* env = aura::Env::GetInstance();
ASSERT_TRUE(env);
RunTestSequence(Steps(
SetTabletMode(ShouldUseTabletMode()),
LaunchSystemWebApp(ash::SystemWebAppType::PERSONALIZATION),
WaitForShow(ash::SystemNudgeView::kBubbleIdForTesting),
PressButton(ash::SystemNudgeView::kSecondaryButtonIdForTesting),
WaitForHide(ash::SystemNudgeView::kBubbleIdForTesting),
WithoutDelay(Steps(
CheckHistogramCounts("Ash.Growth.Ui.Impression.Campaigns500", 100, 1),
CheckHistogramCounts(
"Ash.Growth.Ui.ButtonPressed.Button0.Campaigns500", 100, 0),
CheckHistogramCounts(
"Ash.Growth.Ui.ButtonPressed.Button1.Campaigns500", 100, 1),
CheckHistogramCounts("Ash.Growth.Ui.Dismissed.Campaigns500", 100,
1)))));
}
// CampaignsManagerInteractiveUiNotificationTest
// ----------------------------------
class CampaignsManagerInteractiveUiNotificationTest
: public CampaignsManagerInteractiveUiTest,
public testing::WithParamInterface<bool> {
public:
CampaignsManagerInteractiveUiNotificationTest() {
base::WriteFile(GetCampaignsFilePath(temp_dir_), kCampaignsNotification);
}
void SetUpOnMainThread() override {
InProcessBrowserTest::SetUpOnMainThread();
InteractiveAshTest::SetupContextWidget();
}
protected:
message_center::Notification* GetNotification() {
return message_center::MessageCenter::Get()->FindNotificationById(
"growth_campaign_101");
}
// If a notification with `notification_id` is displayed, simulates clicking
// on that notification with `button_index` button.
auto Click(std::optional<int> button_index) {
return Do([=, this]() {
GetNotification()->delegate()->Click(button_index, std::nullopt);
});
}
bool ShouldUseTabletMode() { return GetParam(); }
};
INSTANTIATE_TEST_SUITE_P(,
CampaignsManagerInteractiveUiNotificationTest,
::testing::Bool());
IN_PROC_BROWSER_TEST_P(CampaignsManagerInteractiveUiNotificationTest,
ShowNotification) {
RunTestSequence(
SetTabletMode(ShouldUseTabletMode()),
WaitForShow(ShowNotificationActionPerformer::kBubbleIdForTesting),
WithoutDelay(Steps(
CheckHistogramCounts("Ash.Growth.Ui.Impression.Campaigns500", 101, 1),
CheckHistogramCounts(
"Ash.Growth.Ui.ButtonPressed.Button0.Campaigns500", 101, 0),
CheckHistogramCounts(
"Ash.Growth.Ui.ButtonPressed.Button1.Campaigns500", 101, 0),
CheckHistogramCounts("Ash.Growth.Ui.Dismissed.Campaigns500", 101, 0),
ToggleTabletMode())));
}
IN_PROC_BROWSER_TEST_P(CampaignsManagerInteractiveUiNotificationTest,
ClickPrimaryButtonOnNotification) {
aura::Env* env = aura::Env::GetInstance();
ASSERT_TRUE(env);
RunTestSequence(
SetTabletMode(ShouldUseTabletMode()),
WaitForShow(ShowNotificationActionPerformer::kBubbleIdForTesting),
Click(/*button_index=*/0),
WaitForHide(ShowNotificationActionPerformer::kBubbleIdForTesting),
WaitForWindowWithTitle(env, u"www.google.com"),
WithoutDelay(Steps(
CheckHistogramCounts("Ash.Growth.Ui.Impression.Campaigns500", 101, 1),
CheckHistogramCounts(
"Ash.Growth.Ui.ButtonPressed.Button0.Campaigns500", 101, 1),
CheckHistogramCounts(
"Ash.Growth.Ui.ButtonPressed.Button1.Campaigns500", 101, 0),
CheckHistogramCounts("Ash.Growth.Ui.Dismissed.Campaigns500", 101,
0))));
}
IN_PROC_BROWSER_TEST_P(CampaignsManagerInteractiveUiNotificationTest,
ClickSecondaryButtonOnNotification) {
aura::Env* env = aura::Env::GetInstance();
ASSERT_TRUE(env);
RunTestSequence(
SetTabletMode(ShouldUseTabletMode()),
WaitForShow(ShowNotificationActionPerformer::kBubbleIdForTesting),
Click(/*button_index=*/1),
WaitForHide(ShowNotificationActionPerformer::kBubbleIdForTesting),
WithoutDelay(Steps(
CheckHistogramCounts("Ash.Growth.Ui.Impression.Campaigns500", 101, 1),
CheckHistogramCounts(
"Ash.Growth.Ui.ButtonPressed.Button0.Campaigns500", 101, 0),
CheckHistogramCounts(
"Ash.Growth.Ui.ButtonPressed.Button1.Campaigns500", 101, 1),
CheckHistogramCounts("Ash.Growth.Ui.Dismissed.Campaigns500", 101,
0))));
}