chromium/chrome/browser/ui/webui/ash/settings/integration_tests/privacy_hub_interactive_uitest.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.

#include <vector>

#include "ash/constants/ash_features.h"
#include "ash/webui/settings/public/constants/routes.mojom.h"
#include "base/check.h"
#include "base/functional/bind.h"
#include "base/functional/callback_forward.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/ui/chrome_pages.h"
#include "chrome/browser/ui/settings_window_manager_chromeos.h"
#include "chrome/test/base/ash/interactive/interactive_ash_test.h"
#include "chrome/test/interaction/interactive_browser_test.h"
#include "ui/base/interaction/element_identifier.h"

namespace ash {
namespace {

enum class SensorType { kCamera, kMicrophone, kGeolocation };
enum class ControlElementType { kToggle, kSubpageLink, kLegacyToggle };

// Base class for privacy hub tests in this file.
class PrivacyHubInteractiveUiTest : public InteractiveAshTest {
 public:
  // Shows OS Settings and loads a sub-page.
  auto ShowOSSettingsSubPage(const std::string& sub_page) {
    return Do([&]() {
      chrome::SettingsWindowManager::GetInstance()->ShowOSSettings(
          GetActiveUserProfile(), sub_page);
    });
  }

  static DeepQuery ControlElementQuery(SensorType sensor,
                                       ControlElementType element_type) {
    const DeepQuery query_prefix{
        "os-settings-ui", "os-settings-main", "main-page-container",
        "os-settings-privacy-page", "settings-privacy-hub-subpage"};
    const std::string last_segment = [element_type, sensor]() -> std::string {
      if (SensorType::kGeolocation == sensor) {
        return "cr-link-row#geolocationAreaLinkRow";
      }

      const std::string html_tag = [element_type]() {
        switch (element_type) {
          case ControlElementType::kToggle:
            return "cr-toggle";
          case ControlElementType::kSubpageLink:
            return "cr-link-row";
          case ControlElementType::kLegacyToggle:
            return "settings-toggle-button";
        }
      }();
      const std::string element_id_prefix =
          (sensor == SensorType::kCamera) ? "camera" : "microphone";
      const std::string element_id_suffix =
          (element_type == ControlElementType::kSubpageLink) ? "SubpageLink"
                                                             : "Toggle";

      return html_tag + "#" + element_id_prefix + element_id_suffix;
    }();

    return query_prefix + last_segment;
  }

  // InteractiveAshTest:
  void SetUpOnMainThread() override {
    InteractiveAshTest::SetUpOnMainThread();

    // Set up context for element tracking for InteractiveBrowserTest.
    SetupContextWidget();

    // Ensure the OS Settings system web app (SWA) is installed.
    InstallSystemApps();
  }
};

class PrivacyHubSettingsPageTest
    : public PrivacyHubInteractiveUiTest,
      public testing::WithParamInterface<std::tuple<bool, bool>> {
 public:
  PrivacyHubSettingsPageTest() {
    using FeatureList = std::vector<base::test::FeatureRef>;
    FeatureList enabled_features;
    FeatureList disabled_features;
    const auto feature_set = [&](bool flag_on) -> FeatureList& {
      return flag_on ? enabled_features : disabled_features;
    };
    feature_set(LocationFlagOn()).push_back(features::kCrosPrivacyHub);
    feature_set(PerAppPermissionFlagOn())
        .push_back(features::kCrosPrivacyHubAppPermissions);
    feature_list_.InitWithFeatures(enabled_features, disabled_features);
  }

  bool LocationFlagOn() const { return std::get<0>(GetParam()); }
  bool PerAppPermissionFlagOn() const { return std::get<1>(GetParam()); }

  auto WaitForElement(bool condition,
                      const ui::ElementIdentifier& element_id,
                      const DeepQuery& query) {
    return condition ? WaitForElementExists(element_id, query)
                     : WaitForElementDoesNotExist(element_id, query);
  }

 private:
  base::test::ScopedFeatureList feature_list_;
};

// This test checks for the presence of the control elements for all sensors
// (camera, microphone, geolocation). The particular control elements and the
// set of sensors differs based on the feature flags. This test verifies the
// right composition of control elements on the Privacy Controls subpage in
// settings for all combinations of feature flags.
IN_PROC_BROWSER_TEST_P(PrivacyHubSettingsPageTest, PrivacyControls) {
  DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kOsSettingsWebContentsId);
  ASSERT_EQ(features::IsCrosPrivacyHubLocationEnabled(), LocationFlagOn());
  ASSERT_EQ(features::IsCrosPrivacyHubAppPermissionsEnabled(),
            PerAppPermissionFlagOn());

  RunTestSequence(
      Log("Opening OS settings system web app"),
      InstrumentNextTab(kOsSettingsWebContentsId, AnyBrowser()),
      ShowOSSettingsSubPage(chromeos::settings::mojom::kPrivacyHubSubpagePath),
      WaitForShow(kOsSettingsWebContentsId),

      Log("Waiting for OS settings privacy hub page to load"),
      WaitForWebContentsReady(
          kOsSettingsWebContentsId,
          chrome::GetOSSettingsUrl(
              chromeos::settings::mojom::kPrivacyHubSubpagePath)),

      Log("Waiting for camera subpage trigger to exist or not"),
      WaitForElement(PerAppPermissionFlagOn(), kOsSettingsWebContentsId,
                     ControlElementQuery(SensorType::kCamera,
                                         ControlElementType::kSubpageLink)),

      Log("Waiting for the camera toggle button to exist or not"),
      WaitForElement(PerAppPermissionFlagOn(), kOsSettingsWebContentsId,
                     ControlElementQuery(SensorType::kCamera,
                                         ControlElementType::kToggle)),

      Log("Waiting for the legacy camera toggle button to exist or not"),
      WaitForElement(!PerAppPermissionFlagOn(), kOsSettingsWebContentsId,
                     ControlElementQuery(SensorType::kCamera,
                                         ControlElementType::kLegacyToggle)),

      Log("Waiting for microphone subpage trigger to exist or not"),
      WaitForElement(PerAppPermissionFlagOn(), kOsSettingsWebContentsId,
                     ControlElementQuery(SensorType::kMicrophone,
                                         ControlElementType::kSubpageLink)),

      Log("Waiting for the microphone toggle button to exist or not"),
      WaitForElement(PerAppPermissionFlagOn(), kOsSettingsWebContentsId,
                     ControlElementQuery(SensorType::kMicrophone,
                                         ControlElementType::kToggle)),

      Log("Waiting for the legacy microphone toggle button to exist or not"),
      WaitForElement(!PerAppPermissionFlagOn(), kOsSettingsWebContentsId,
                     ControlElementQuery(SensorType::kMicrophone,
                                         ControlElementType::kLegacyToggle)),

      Log("Waiting for geolocation subpage trigger to exist or not"),
      WaitForElement(LocationFlagOn(), kOsSettingsWebContentsId,
                     ControlElementQuery(SensorType::kGeolocation,
                                         ControlElementType::kSubpageLink)),

      Log("Test complete"));
}

INSTANTIATE_TEST_SUITE_P(All,
                         PrivacyHubSettingsPageTest,
                         testing::Combine(
                             /*location_on=*/testing::Bool(),
                             /*per_app_permissions_on=*/testing::Bool()));

// Tests for privacy hub app permissions feature.
class PrivacyHubAppPermissionsInteractiveUiTest
    : public PrivacyHubInteractiveUiTest {
 public:
  PrivacyHubAppPermissionsInteractiveUiTest() {
    feature_list_.InitWithFeatures(
        /*enabled_features=*/{features::kCrosPrivacyHub,
                              features::kCrosPrivacyHubAppPermissions},
        /*disabled_features=*/{});
    CHECK(features::IsCrosPrivacyHubLocationEnabled());
    CHECK(features::IsCrosPrivacyHubAppPermissionsEnabled());
  }

 private:
  base::test::ScopedFeatureList feature_list_;
};

IN_PROC_BROWSER_TEST_F(PrivacyHubAppPermissionsInteractiveUiTest,
                       CameraSubpage) {
  DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kOsSettingsWebContentsId);

  RunTestSequence(
      Log("Opening OS settings system web app"),
      InstrumentNextTab(kOsSettingsWebContentsId, AnyBrowser()),
      ShowOSSettingsSubPage(
          chromeos::settings::mojom::kPrivacyHubCameraSubpagePath),
      WaitForShow(kOsSettingsWebContentsId),

      Log("Waiting for OS settings privacy hub camera subpage to load"),
      WaitForWebContentsReady(
          kOsSettingsWebContentsId,
          chrome::GetOSSettingsUrl(
              chromeos::settings::mojom::kPrivacyHubCameraSubpagePath)),

      Log("Test complete"));
}

IN_PROC_BROWSER_TEST_F(PrivacyHubAppPermissionsInteractiveUiTest,
                       MicrophoneSubpage) {
  DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kOsSettingsWebContentsId);

  RunTestSequence(
      Log("Opening OS settings system web app"),
      InstrumentNextTab(kOsSettingsWebContentsId, AnyBrowser()),
      ShowOSSettingsSubPage(
          chromeos::settings::mojom::kPrivacyHubMicrophoneSubpagePath),
      WaitForShow(kOsSettingsWebContentsId),

      Log("Waiting for OS settings privacy hub microphone subpage to load"),
      WaitForWebContentsReady(
          kOsSettingsWebContentsId,
          chrome::GetOSSettingsUrl(
              chromeos::settings::mojom::kPrivacyHubMicrophoneSubpagePath)),

      Log("Test complete"));
}

IN_PROC_BROWSER_TEST_F(PrivacyHubAppPermissionsInteractiveUiTest,
                       GeolocationSubpage) {
  DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kOsSettingsWebContentsId);

  RunTestSequence(
      Log("Opening OS settings system web app"),
      InstrumentNextTab(kOsSettingsWebContentsId, AnyBrowser()),
      ShowOSSettingsSubPage(
          chromeos::settings::mojom::kPrivacyHubGeolocationSubpagePath),
      WaitForShow(kOsSettingsWebContentsId),

      Log("Waiting for OS settings privacy hub geolocation subpage to load"),
      WaitForWebContentsReady(
          kOsSettingsWebContentsId,
          chrome::GetOSSettingsUrl(
              chromeos::settings::mojom::kPrivacyHubGeolocationSubpagePath)),

      Log("Test complete"));
}

}  // namespace
}  // namespace ash