chromium/chrome/browser/ash/scalable_iph/scalable_iph_e2e_browsertest.cc

// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include <string_view>

#include "ash/constants/ash_features.h"
#include "ash/constants/ash_switches.h"
#include "ash/public/cpp/shelf_model.h"
#include "base/no_destructor.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/ash/scalable_iph/scalable_iph_browser_test_base.h"
#include "chrome/browser/ash/system_web_apps/system_web_app_manager.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/web_applications/web_app_id_constants.h"
#include "chrome/common/chrome_switches.h"
#include "chromeos/ash/components/scalable_iph/scalable_iph.h"
#include "components/feature_engagement/test/scoped_iph_feature_list.h"
#include "components/variations/field_trial_config/fieldtrial_testing_config.h"
#include "content/public/test/browser_test.h"

namespace {

constexpr char kStudyName[] = "ScalableIphStudy";
constexpr size_t kParamsSizeLimit = 40 << 10;  // 40 KiByte

bool IsGoogleChrome() {
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
  return true;
#else
  return false;
#endif  // BUILDFLAG(GOOGLE_CHROME_BRANDING)
}

const variations::FieldTrialTestingStudy* FindStudy() {
  const variations::FieldTrialTestingConfig& config =
      variations::kFieldTrialConfig;
  for (size_t i = 0; i < config.studies_size; ++i) {
    const variations::FieldTrialTestingStudy* study = config.studies + i;
    if (std::string(study->name) == kStudyName) {
      return study;
    }
  }
  return nullptr;
}

const variations::FieldTrialTestingExperiment* FindExperiment(
    const variations::FieldTrialTestingStudy* study,
    const std::string& experiment_name) {
  for (size_t i = 0; i < study->experiments_size; ++i) {
    const variations::FieldTrialTestingExperiment* experiment =
        study->experiments + i;
    if (experiment->name == experiment_name) {
      return experiment;
    }
  }
  return nullptr;
}

const base::Feature* GetFeature(const std::string_view& feature_name) {
  static const base::NoDestructor<std::vector<const base::Feature*>>
      supported_features({&ash::features::kScalableIph,
                          &ash::features::kHelpAppWelcomeTips,
                          &ash::features::kShelfLauncherNudge});
  for (const base::Feature* feature : *supported_features) {
    if (feature->name == feature_name) {
      return feature;
    }
  }

  feature_engagement::FeatureVector features =
      feature_engagement::GetAllFeatures();
  for (const base::Feature* feature : features) {
    if (feature->name == feature_name) {
      return feature;
    }
  }

  CHECK(false) << feature_name << " was not found.";

  return nullptr;
}

base::FieldTrialParams GetFieldTrialParams(
    const std::string_view& prefix,
    const variations::FieldTrialTestingExperiment* experiment,
    size_t& params_size) {
  base::FieldTrialParams params;

  for (size_t i = 0; i < experiment->params_size; ++i) {
    const variations::FieldTrialTestingExperimentParams* experiment_params =
        experiment->params + i;
    std::string param_key(experiment_params->key);
    if (!base::StartsWith(param_key, prefix)) {
      continue;
    }

    std::string param_value(experiment_params->value);
    params[param_key] = param_value;

    params_size += param_key.size();
    params_size += param_value.size();
  }

  return params;
}

void ApplyExperiment(
    feature_engagement::test::ScopedIphFeatureList* scoped_feature_list,
    const variations::FieldTrialTestingExperiment* experiment) {
  size_t params_size = 0;
  std::vector<base::test::FeatureRefAndParams> enable_features;
  for (size_t i = 0; i < experiment->enable_features_size; ++i) {
    std::string_view feature_name(*(experiment->enable_features + i));

    // `ScopedIphFeatureList` uses `ScopedFeatureList` internally.
    // `ScopedFeatureList` creates a field trial for each feature. Parse params
    // and collect related params to a feature. This is test specific behavior.
    // IIUC single field trial is created for all features in a single study in
    // production code.
    enable_features.push_back(base::test::FeatureRefAndParams(
        *GetFeature(feature_name),
        GetFieldTrialParams(feature_name, experiment, params_size)));
  }

  // Ignore non-param part (e.g. Flags) size as it should be small enough.
  CHECK(params_size < kParamsSizeLimit)
      << "Config contains params of size with " << params_size
      << ". Our internal config size limit of ScalableIph component is "
      << kParamsSizeLimit
      << ". Field trial data has 256KiByte size limit. We share the limit with "
         "other field trials. See kFieldTrialAllocationSize in field_trial.cc "
         "about details.";

  std::vector<base::test::FeatureRef> disable_features;
  for (size_t i = 0; i < experiment->disable_features_size; ++i) {
    const char* const* feature_name = experiment->disable_features + i;
    disable_features.push_back(
        base::test::FeatureRef(*GetFeature(std::string(*feature_name))));
  }

  // Enable `ScalableIphDebug` for easier debug.
  enable_features.push_back(
      base::test::FeatureRefAndParams(ash::features::kScalableIphDebug, {}));

  scoped_feature_list->InitAndEnableFeaturesWithParameters(enable_features,
                                                           disable_features);
}

class ScalableIphE2EBrowserTest : public ash::ScalableIphBrowserTestBase {
 public:
  explicit ScalableIphE2EBrowserTest(const std::string& experiment_name)
      : experiment_name_(experiment_name) {
    enable_mock_tracker_ = false;
  }

  void InitializeScopedFeatureList() override {
    const variations::FieldTrialTestingStudy* study = FindStudy();
    CHECK(study) << kStudyName
                 << " was not found in fieldtrial testing config.";

    const variations::FieldTrialTestingExperiment* experiment =
        FindExperiment(study, experiment_name_);
    CHECK(experiment) << experiment_name_ << "was not found in the study.";

    ApplyExperiment(&scoped_iph_feature_list_, experiment);
  }

  void SetUpDefaultCommandLine(base::CommandLine* command_line) override {
    ScalableIphBrowserTestBase::SetUpDefaultCommandLine(command_line);

    command_line->RemoveSwitch(switches::kDisableDefaultApps);
    command_line->AppendSwitch(
        ash::switches::kAllowDefaultShelfPinLayoutIgnoringSync);
  }

  void SetUpOnMainThread() override {
    ash::ScalableIphBrowserTestBase::SetUpOnMainThread();

    mock_delegate()->FakeShowBubble();
    mock_delegate()->FakeShowNotification();

    ash::SystemWebAppManager::GetForTest(browser()->profile())
        ->InstallSystemAppsForTesting();

    // Note that there is an async operation before network status change has
    // been propagated to `ScalableIph`. This does NOT wait it.
    AddOnlineNetwork();
  }

 private:
  const std::string experiment_name_;

  // TODO(b/285225729): consider if we can use `ScopedIphFeatureList` in
  // `ScalableIphBrowserTestBase`.
  feature_engagement::test::ScopedIphFeatureList scoped_iph_feature_list_;
};

class ScalableIphE2EBrowserTestCounterfactualControl
    : public ScalableIphE2EBrowserTest {
 public:
  ScalableIphE2EBrowserTestCounterfactualControl()
      : ScalableIphE2EBrowserTest("CounterfactualControl_BETA_20231204") {}
};

class ScalableIphE2EBrowserTestUnlockedBased
    : public ScalableIphE2EBrowserTest {
 public:
  ScalableIphE2EBrowserTestUnlockedBased()
      : ScalableIphE2EBrowserTest("UnlockedBased_BETA_20231204") {}
};

class ScalableIphE2EBrowserTestTimerBased : public ScalableIphE2EBrowserTest {
 public:
  ScalableIphE2EBrowserTestTimerBased()
      : ScalableIphE2EBrowserTest("TimerBased_BETA_20231204") {}
};

class ScalableIphE2EBrowserTestHelpAppBased : public ScalableIphE2EBrowserTest {
 public:
  ScalableIphE2EBrowserTestHelpAppBased()
      : ScalableIphE2EBrowserTest("HelpAppBased_BETA_20231204") {}
};

}  // namespace

IN_PROC_BROWSER_TEST_F(ScalableIphE2EBrowserTestCounterfactualControl, E2E) {
  if (!IsGoogleChrome()) {
    GTEST_SKIP() << "E2E tests are designed to be run under Google Chrome";
  }

  EXPECT_FALSE(ash::ShelfModel::Get()->IsAppPinned(web_app::kHelpAppId));

  // TODO(b/285225729): add more expectations to test the config.
}

IN_PROC_BROWSER_TEST_F(ScalableIphE2EBrowserTestUnlockedBased, E2E) {
  if (!IsGoogleChrome()) {
    GTEST_SKIP() << "E2E tests are designed to be run under Google Chrome";
  }

  EXPECT_FALSE(ash::ShelfModel::Get()->IsAppPinned(web_app::kHelpAppId));

  // TODO(b/285225729): add more expectations to test the config.
}

IN_PROC_BROWSER_TEST_F(ScalableIphE2EBrowserTestTimerBased, E2E) {
  if (!IsGoogleChrome()) {
    GTEST_SKIP() << "E2E tests are designed to be run under Google Chrome";
  }

  EXPECT_FALSE(ash::ShelfModel::Get()->IsAppPinned(web_app::kHelpAppId));

  // TODO(b/285225729): add more expectations to test the config.
}

IN_PROC_BROWSER_TEST_F(ScalableIphE2EBrowserTestHelpAppBased, E2E) {
  if (!IsGoogleChrome()) {
    GTEST_SKIP() << "E2E tests are designed to be run under Google Chrome";
  }

  EXPECT_TRUE(ash::ShelfModel::Get()->IsAppPinned(web_app::kHelpAppId));

  // TODO(b/285225729): add more expectations to test the config.
}