chromium/chrome/test/base/ash/interactive/vpn/vpn_ui_interactive_uitest.cc

// 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 "ash/ash_element_identifiers.h"
#include "base/strings/string_util.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/test/base/ash/interactive/interactive_ash_test.h"
#include "chrome/test/base/ash/interactive/settings/interactive_uitest_elements.h"
#include "chromeos/ash/components/dbus/shill/fake_shill_profile_client.h"
#include "chromeos/ash/components/dbus/shill/fake_shill_service_client.h"
#include "chromeos/ash/components/network/network_cert_loader.h"
#include "chromeos/strings/grit/chromeos_strings.h"
#include "components/user_manager/user_manager.h"
#include "crypto/scoped_nss_types.h"
#include "crypto/scoped_test_nss_db.h"
#include "dbus/object_path.h"
#include "net/cert/nss_cert_database_chromeos.h"
#include "third_party/cros_system_api/dbus/shill/dbus-constants.h"
#include "ui/base/interaction/element_identifier.h"
#include "ui/base/l10n/l10n_util.h"

namespace ash {
namespace {

DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kOSSettingsId);

constexpr char kProfilePath[] = "/network/profile/0";
constexpr char kVpnServiceName[] = "vpn_service_name";
constexpr char kVpnHostname[] = "vpn_host_name";
constexpr char kOpenVpnUsername[] = "username";
constexpr char kOpenVpnPassword[] = "password123";

void CheckPropertyStringValue(const base::Value::Dict* dict,
                              const std::string& key,
                              const std::string& expected) {
  const std::string* actual = dict->FindString(key);
  ASSERT_TRUE(actual);
  EXPECT_EQ(*actual, expected);
}

class VpnUiInteractiveUiTest : public InteractiveAshTest {
 protected:
  // InteractiveAshTest:
  void SetUpOnMainThread() override {
    InteractiveAshTest::SetUpOnMainThread();

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

    // Ensure the OS Settings app is installed.
    InstallSystemApps();

    ShillServiceClient::Get()->GetTestInterface()->ClearServices();
    ShillProfileClient::Get()->GetTestInterface()->AddProfile(
        kProfilePath,
        user_manager::UserManager::Get()->GetActiveUser()->username_hash());
    ShillProfileClient::Get()->SetProperty(
        dbus::ObjectPath(kProfilePath), shill::kAlwaysOnVpnModeProperty,
        base::Value(shill::kAlwaysOnVpnModeOff), base::DoNothing(),
        base::DoNothing());

    // Load a fake cert database to user NSSDB. VPN network has to be connected
    // after the NetworkCertLoader finishes loading initial certs.
    test_nsscertdb_ = std::make_unique<net::NSSCertDatabaseChromeOS>(
        crypto::ScopedPK11Slot(PK11_ReferenceSlot(test_nssdb_.slot())),
        crypto::ScopedPK11Slot(PK11_ReferenceSlot(test_nssdb_.slot())));
    NetworkCertLoader::ForceAvailableForNetworkAuthForTesting();
    NetworkCertLoader::Get()->SetUserNSSDB(test_nsscertdb_.get());
  }

 private:
  crypto::ScopedTestNSSDB test_nssdb_;
  std::unique_ptr<net::NSSCertDatabaseChromeOS> test_nsscertdb_;
};

IN_PROC_BROWSER_TEST_F(VpnUiInteractiveUiTest,
                       BuiltInOpenVpnAddConnectDisconnectForget) {
  ui::ElementContext context =
      LaunchSystemWebApp(SystemWebAppType::SETTINGS, kOSSettingsId);

  // Run the following steps with the OS Settings context set as the default.
  RunTestSequenceInContext(
      context,

      Log("Navigating to the internet page"),

      NavigateSettingsToInternetPage(kOSSettingsId),

      Log("Open add built-in VPN dialog"),

      OpenAddBuiltInVpnDialog(kOSSettingsId),

      Log("Verify expected dialog title shows"),

      WaitForElementTextContains(
          kOSSettingsId, settings::InternetConfigDialogTitle(),
          l10n_util::GetStringFUTF8(
              IDS_SETTINGS_INTERNET_JOIN_TYPE,
              l10n_util::GetStringUTF16(IDS_NETWORK_TYPE_VPN))),

      Log("Verify the connect button is disabled before input the config"),

      WaitForElementDisabled(kOSSettingsId,
                             settings::vpn::JoinVpnDialogConnectButton()),

      Log("Enter VPN service name"),

      WaitForElementEnabled(kOSSettingsId,
                            settings::vpn::JoinVpnDialogServiceNameInput()),
      ClickElement(kOSSettingsId,
                   settings::vpn::JoinVpnDialogServiceNameInput()),
      SendTextAsKeyEvents(kOSSettingsId, kVpnServiceName),

      Log("Enter VPN hostname"),

      WaitForElementEnabled(kOSSettingsId,
                            settings::vpn::JoinVpnDialogHostnameInput()),
      ClickElement(kOSSettingsId, settings::vpn::JoinVpnDialogHostnameInput()),
      SendTextAsKeyEvents(kOSSettingsId, kVpnHostname),

      Log("Choose VPN provider type"),

      WaitForElementEnabled(kOSSettingsId,
                            settings::vpn::JoinVpnDialogProviderTypeSelect()),
      ClickElement(kOSSettingsId,
                   settings::vpn::JoinVpnDialogProviderTypeSelect()),
      SelectDropdownElementOption(
          kOSSettingsId, settings::vpn::JoinVpnDialogProviderTypeOptions(),
          l10n_util::GetStringUTF8(IDS_ONC_VPN_TYPE_OPENVPN).c_str()),
      ClickElement(kOSSettingsId, settings::InternetConfigDialogTitle()),

      Log("Enter VPN username"),

      WaitForElementEnabled(kOSSettingsId,
                            settings::vpn::JoinVpnDialogOpenVpnUsernameInput()),
      MoveMouseTo(kOSSettingsId,
                  settings::vpn::JoinVpnDialogOpenVpnUsernameInput()),
      ClickElement(kOSSettingsId,
                   settings::vpn::JoinVpnDialogOpenVpnUsernameInput()),
      SendTextAsKeyEvents(kOSSettingsId, kOpenVpnUsername),

      Log("Enter VPN password"),

      WaitForElementEnabled(kOSSettingsId,
                            settings::vpn::JoinVpnDialogOpenVpnPasswordInput()),
      ClickElement(kOSSettingsId,
                   settings::vpn::JoinVpnDialogOpenVpnPasswordInput()),
      SendTextAsKeyEvents(kOSSettingsId, kOpenVpnPassword),

      Log("Checking for OTP and save credentials element shows"),

      WaitForElementEnabled(kOSSettingsId,
                            settings::vpn::JoinVpnDialogOpenVpnOtpInput()),
      WaitForElementEnabled(
          kOSSettingsId, settings::vpn::JoinVpnDialogSaveCredentialsToggle()),

      Log("Click on \"Connect\" button"),

      WaitForElementEnabled(kOSSettingsId,
                            settings::vpn::JoinVpnDialogConnectButton()),
      ClickElement(kOSSettingsId, settings::vpn::JoinVpnDialogConnectButton()),

      Log("Verify VPN summary item has expected label"),

      WaitForElementEnabled(kOSSettingsId, settings::vpn::VpnSummaryItem()),
      WaitForElementTextContains(kOSSettingsId, settings::vpn::VpnSummaryItem(),
                                 kVpnServiceName),

      Log("Verify the expected VPN service is created in shill"),

      Do([&]() {
        const std::string vpn_service_path =
            ShillServiceClient::Get()
                ->GetTestInterface()
                ->FindServiceMatchingName(kVpnServiceName);
        ASSERT_FALSE(vpn_service_path.empty());
        const base::Value::Dict* properties =
            ShillServiceClient::Get()->GetTestInterface()->GetServiceProperties(
                vpn_service_path);
        ASSERT_TRUE(properties);
        const base::Value::Dict* provider =
            properties->FindDict(shill::kProviderProperty);
        ASSERT_TRUE(provider);
        CheckPropertyStringValue(provider, shill::kHostProperty, kVpnHostname);
        CheckPropertyStringValue(provider, shill::kOpenVPNUserProperty,
                                 kOpenVpnUsername);
        CheckPropertyStringValue(provider, shill::kOpenVPNPasswordProperty,
                                 kOpenVpnPassword);
        CheckPropertyStringValue(provider, shill::kTypeProperty,
                                 shill::kProviderOpenVpn);
      }),

      Log("Navigate to VPN detail subpage"),

      NavigateToInternetDetailsPage(kOSSettingsId, NetworkTypePattern::VPN(),
                                    kVpnServiceName),
      WaitForElementTextContains(
          kOSSettingsId, settings::SettingsSubpageNetworkState(),
          /*text=*/l10n_util::GetStringUTF8(IDS_ONC_CONNECTED).c_str()),

      Log("Checking for the expected UI elements"),

      WaitForElementTextContains(
          kOSSettingsId, ash::settings::vpn::VpnSubpageProviderType(),
          l10n_util::GetStringUTF8(IDS_ONC_VPN_TYPE_OPENVPN).c_str()),
      WaitForElementExists(kOSSettingsId,
                           ash::settings::vpn::VpnSubpageHostnameInput()),

      CheckJsResultAt(
          kOSSettingsId, ash::settings::vpn::VpnSubpageHostnameInput(),
          base::StringPrintf("(el) => el.value === '%s'", kVpnHostname)),
      WaitForElementExists(kOSSettingsId,
                           ash::settings::vpn::VpnSubpageUsernameInput()),
      CheckJsResultAt(
          kOSSettingsId, ash::settings::vpn::VpnSubpageUsernameInput(),
          base::StringPrintf("(el) => el.value === '%s'", kOpenVpnUsername)),

      Log("Disconnect VPN network"),

      WaitForElementEnabled(
          kOSSettingsId,
          ash::settings::SettingsSubpageConnectDisconnectButton()),
      ClickElement(kOSSettingsId,
                   ash::settings::SettingsSubpageConnectDisconnectButton()),
      WaitForElementTextContains(
          kOSSettingsId, settings::SettingsSubpageNetworkState(),
          /*text=*/l10n_util::GetStringUTF8(IDS_ONC_NOT_CONNECTED).c_str()),

      Log("Forget VPN network"),

      WaitForElementEnabled(kOSSettingsId,
                            ash::settings::SettingsSubpageForgetButton()),
      ClickElement(kOSSettingsId, ash::settings::SettingsSubpageForgetButton()),

      Log("Verify the VPN doesn't appear in the list after forget"),

      WaitForElementDisplayNone(kOSSettingsId,
                                ash::settings::vpn::VpnNetworksList()),

      Log("Test complete"));
}

}  // namespace
}  // namespace ash