chromium/chrome/browser/ui/webui/ash/add_supervision/add_supervision_ui_browsertest.cc

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

#include <string>

#include "base/strings/strcat.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/metrics/user_action_tester.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/webui/ash/add_supervision/add_supervision_metrics_recorder.h"
#include "chrome/browser/ui/webui/ash/add_supervision/add_supervision_ui.h"
#include "chrome/browser/ui/webui/ash/add_supervision/confirm_signout_dialog.h"
#include "chrome/common/webui_url_constants.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/signin/public/identity_manager/identity_test_environment.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/network_connection_change_simulator.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "third_party/cros_system_api/dbus/service_constants.h"

namespace ash {

namespace {
// Element references for use in test scripts.
const char kGetAddSupervisionAppElementJS[] =
    "document.querySelector('add-supervision-app')";

const char kGetAddSupervisionUIElementJS[] =
    "document.querySelector('add-supervision-app')"
    ".shadowRoot.querySelector('add-supervision-ui')";

const char kGetSupervisedUserOfflineElementJS[] =
    "document.querySelector('add-supervision-app')"
    ".shadowRoot.querySelector('supervised-user-offline')";

const char kGetSupervisedUserErrorElementJS[] =
    "document.querySelector('add-supervision-app')"
    ".shadowRoot.querySelector('supervised-user-error')";
}  // namespace

// Base class for AddSupervision tests.
class AddSupervisionBrowserTest : public InProcessBrowserTest {
 public:
  AddSupervisionBrowserTest() = default;

  AddSupervisionBrowserTest(const AddSupervisionBrowserTest&) = delete;
  AddSupervisionBrowserTest& operator=(const AddSupervisionBrowserTest&) =
      delete;

  ~AddSupervisionBrowserTest() override = default;

  void SetUpOnMainThread() override {
    embedded_test_server()->StartAcceptingConnections();
    // TODO(danan):  See if this is possible to do this instead using
    // FakeGaia.IssueOAuthToken().
    identity_test_env_ = std::make_unique<signin::IdentityTestEnvironment>();
    identity_test_env_->MakePrimaryAccountAvailable(
        "[email protected]", signin::ConsentLevel::kSync);
    // This makes the identity manager return the string "access_token" for the
    // access token.
    identity_test_env_->SetAutomaticIssueOfAccessTokens(true);
    AddSupervisionUI::SetUpForTest(identity_test_env_->identity_manager());

    // Set start_time_ so that the DCHECK(!start_time_.is_null()) in
    // AddSupervisionMetricsRecorder::RecordUserTime() doesn't throw.
    AddSupervisionMetricsRecorder::GetInstance()
        ->RecordAddSupervisionEnrollment(
            AddSupervisionMetricsRecorder::EnrollmentState::kInitiated);
  }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    embedded_test_server()->ServeFilesFromSourceDirectory("chrome/test/data");
    ASSERT_TRUE(embedded_test_server()->InitializeAndListen());
    command_line->AppendSwitchASCII(
        "add-supervision-url",
        embedded_test_server()->GetURL("/supervised_user/simple.html").spec());
  }

  AddSupervisionUI* GetAddSupervisionUI() {
    return static_cast<AddSupervisionUI*>(
        contents()->GetWebUI()->GetController());
  }

  content::WebContents* contents() {
    return browser()->tab_strip_model()->GetActiveWebContents();
  }

  GURL settings_webui_url() { return GURL(chrome::kChromeUISettingsURL); }

  GURL add_supervision_webui_url() {
    return GURL(chrome::kChromeUIAddSupervisionURL);
  }

  bool IsScreenActive(const std::string& element_selector) {
    std::string is_active = element_selector + ".classList.contains('active');";
    return content::EvalJs(contents(), is_active).ExtractBool();
  }

 private:
  std::unique_ptr<signin::IdentityTestEnvironment> identity_test_env_;
};

IN_PROC_BROWSER_TEST_F(AddSupervisionBrowserTest, URLParameters) {
  // Open the Add Supervision URL.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), add_supervision_webui_url()));
  EXPECT_TRUE(content::WaitForLoadStop(contents()));

  // Get the URL from the embedded webview.
  std::string webview_url =
      content::EvalJs(
          contents(),
          base::StrCat(
              {kGetAddSupervisionUIElementJS,
               ".shadowRoot.querySelector('#webview').getAttribute('src')",
               ";"}))
          .ExtractString();

  GURL webview_gurl(webview_url);
  ASSERT_TRUE(webview_gurl.has_query());

  // Split the query string into a map of keys to values.
  std::string query_str = webview_gurl.query();
  url::Component query(0, query_str.length());
  url::Component key;
  url::Component value;
  std::map<std::string, std::string> query_parts;
  while (url::ExtractQueryKeyValue(query_str, &query, &key, &value)) {
    query_parts[query_str.substr(key.begin, key.len)] =
        query_str.substr(value.begin, value.len);
  }

  // Validate the query parameters.
  ASSERT_EQ(query_parts.at("flow_type"), "1");
  ASSERT_EQ(query_parts.at("platform_version"),
            base::SysInfo::OperatingSystemVersion());
  ASSERT_EQ(query_parts.at("access_token"), "access_token");
  ASSERT_EQ(query_parts.at("hl"), "en-US");
}

IN_PROC_BROWSER_TEST_F(AddSupervisionBrowserTest, ShowOfflineScreen) {
  // Open the Add Supervision URL.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), add_supervision_webui_url()));
  EXPECT_TRUE(content::WaitForLoadStop(contents()));

  // The online screen should be initially active.
  EXPECT_TRUE(IsScreenActive(std::string(kGetAddSupervisionUIElementJS)));

  EXPECT_FALSE(IsScreenActive(std::string(kGetSupervisedUserOfflineElementJS)));

  // Simulate going offline.
  EXPECT_TRUE(content::ExecJs(
      contents(), "window.dispatchEvent(new CustomEvent('offline'));"));

  // Ensure only the offline screen is active.
  EXPECT_TRUE(IsScreenActive(std::string(kGetSupervisedUserOfflineElementJS)));

  EXPECT_FALSE(IsScreenActive(std::string(kGetAddSupervisionUIElementJS)));

  // Simulate going online.
  EXPECT_TRUE(content::ExecJs(
      contents(), "window.dispatchEvent(new CustomEvent('online'));"));

  // Ensure only the online screen is active.
  EXPECT_TRUE(IsScreenActive(std::string(kGetAddSupervisionUIElementJS)));

  EXPECT_FALSE(IsScreenActive(std::string(kGetSupervisedUserOfflineElementJS)));
}

IN_PROC_BROWSER_TEST_F(AddSupervisionBrowserTest, ShowConfirmSignoutDialog) {
  // Open the Add Supervision URL.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), add_supervision_webui_url()));
  EXPECT_TRUE(content::WaitForLoadStop(contents()));

  // Request that the dialog close before supervision has been enabled.
  ASSERT_TRUE(content::ExecJs(
      contents(), std::string(kGetAddSupervisionUIElementJS) +
                      std::string(".getApiServerForTest().requestClose()")));
  // Confirm that the signout dialog isn't showing
  ASSERT_FALSE(ConfirmSignoutDialog::IsShowing());

  // Simulate supervision being enabled.
  ASSERT_TRUE(content::ExecJs(
      contents(),
      std::string(kGetAddSupervisionUIElementJS) +
          std::string(".getApiServerForTest().notifySupervisionEnabled()")));

  // Request that the dialog is closed again.
  ASSERT_TRUE(content::ExecJs(
      contents(), std::string(kGetAddSupervisionUIElementJS) +
                      std::string(".getApiServerForTest().requestClose()")));

  // Confirm that the dialog is showing.
  ASSERT_TRUE(ConfirmSignoutDialog::IsShowing());
}

IN_PROC_BROWSER_TEST_F(AddSupervisionBrowserTest, UMATest) {
  base::HistogramTester histogram_tester;
  base::UserActionTester user_action_tester;

  // Should see 0 Add Supervision enrollment metrics at first.
  histogram_tester.ExpectTotalCount("AddSupervisionDialog.Enrollment", 0);

  // Should see 0 user actions at first.
  EXPECT_EQ(user_action_tester.GetActionCount(
                "AddSupervisionDialog_AttemptedSignoutAfterEnrollment"),
            0);

  // Open the Add Supervision URL.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), add_supervision_webui_url()));
  EXPECT_TRUE(content::WaitForLoadStop(contents()));

  // Simulate supervision being enabled.
  ASSERT_TRUE(content::ExecJs(
      contents(),
      std::string(kGetAddSupervisionUIElementJS) +
          std::string(".getApiServerForTest().notifySupervisionEnabled()")));

  // Should see 1 Add Supervision process completed.
  histogram_tester.ExpectUniqueSample(
      "AddSupervisionDialog.Enrollment",
      AddSupervisionMetricsRecorder::EnrollmentState::kCompleted, 1);
  histogram_tester.ExpectTotalCount("AddSupervisionDialog.Enrollment", 1);

  // Should see 1 EnrollmentCompleted action.
  EXPECT_EQ(user_action_tester.GetActionCount(
                "AddSupervisionDialog_EnrollmentCompleted"),
            1);
}

IN_PROC_BROWSER_TEST_F(AddSupervisionBrowserTest, ShowErrorScreen) {
  // Open the Add Supervision URL.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), add_supervision_webui_url()));
  EXPECT_TRUE(content::WaitForLoadStop(contents()));

  // The online screen should be initially active.
  EXPECT_TRUE(IsScreenActive(std::string(kGetAddSupervisionUIElementJS)));

  // Simulate an error event.
  EXPECT_TRUE(content::ExecJs(
      contents(), std::string(kGetAddSupervisionAppElementJS) +
                      std::string(".dispatchEvent(new CustomEvent('show-error',"
                                  "{bubbles: true, composed: true}));")));

  // Ensure that the error screen is active.
  EXPECT_TRUE(IsScreenActive(std::string(kGetSupervisedUserErrorElementJS)));

  EXPECT_FALSE(IsScreenActive(std::string(kGetAddSupervisionUIElementJS)));

  // Simulate an offline event.
  EXPECT_TRUE(content::ExecJs(
      contents(), "window.dispatchEvent(new CustomEvent('offline'));"));

  // Ensure that the error screen remains active.
  EXPECT_TRUE(IsScreenActive(std::string(kGetSupervisedUserErrorElementJS)));

  EXPECT_FALSE(IsScreenActive(std::string(kGetSupervisedUserOfflineElementJS)));
}

}  // namespace ash