Skip to content

Commit

Permalink
crosier: Port login.Chrome and login.ChromeGaia
Browse files Browse the repository at this point in the history
- Add `ChromeOSIntegrationLoginMixIn` that provides login support
  - It provides 3 login modes:
    - kStubLogin: Login like browser tests, Good for tests only need to
      verify chrome states.
    - kTestLogin: Login using oobe test api, same as tast's default
      login. Good for test that needs to simulate real login but do
      not need Gaia ideneity;
    - kGaiaLogin: Login using Gaia like production chrome.
- Implement login integration tests that exercise kTestLogin
  (login.Chrome) and kGaiaLogin (login.ChromeGaia).

Bug: b:301445988
Change-Id: If4d3bd0450058ff46b60539b4a12738e7bb88279
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4953973
Reviewed-by: James Cook <jamescook@chromium.org>
Commit-Queue: Xiyuan Xia <xiyuan@chromium.org>
Reviewed-by: Brett Wilson <brettw@chromium.org>
Reviewed-by: Scott Violet <sky@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1212847}
  • Loading branch information
Xiyuan Xia authored and Chromium LUCI CQ committed Oct 20, 2023
1 parent 5b8c4b0 commit 4ca35db
Show file tree
Hide file tree
Showing 9 changed files with 403 additions and 63 deletions.
59 changes: 59 additions & 0 deletions chrome/browser/ash/login/login_integration_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// 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 <string>
#include <vector>

#include "build/branding_buildflags.h"
#include "chrome/browser/ash/login/test/session_manager_state_waiter.h"
#include "chrome/test/base/chromeos/crosier/chromeos_integration_login_mixin.h"
#include "chrome/test/base/chromeos/crosier/interactive_ash_test.h"

class LoginIntegrationTest : public InteractiveAshTest {
public:
LoginIntegrationTest() {
set_exit_when_last_browser_closes(false);

login_mixin().SetMode(ChromeOSIntegrationLoginMixin::Mode::kTestLogin);
}

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

~LoginIntegrationTest() override = default;
};

IN_PROC_BROWSER_TEST_F(LoginIntegrationTest, TestLogin) {
login_mixin().Login();

// Waits for the primary user session to start.
ash::test::WaitForPrimaryUserSessionStart();
}

#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
// Gaia login is only supported for branded build.
class GaiaLoginIntegrationTest : public InteractiveAshTest {
public:
GaiaLoginIntegrationTest() {
set_exit_when_last_browser_closes(false);

// Allows network access for production Gaia.
SetAllowNetworkAccessToHostResolutions();

login_mixin().SetMode(ChromeOSIntegrationLoginMixin::Mode::kGaiaLogin);
}

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

~GaiaLoginIntegrationTest() override = default;
};

IN_PROC_BROWSER_TEST_F(GaiaLoginIntegrationTest, GaiaLogin) {
login_mixin().Login();

// Waits for the primary user session to start.
ash::test::WaitForPrimaryUserSessionStart();
}
#endif // BUILDFLAG(GOOGLE_CHROME_BRANDING)
4 changes: 4 additions & 0 deletions chrome/test/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,8 @@ static_library("test_support") {

if (is_chromeos && is_chromeos_device) {
sources += [
"base/chromeos/crosier/chromeos_integration_login_mixin.cc",
"base/chromeos/crosier/chromeos_integration_login_mixin.h",
"base/chromeos/crosier/chromeos_integration_test_mixin.cc",
"base/chromeos/crosier/chromeos_integration_test_mixin.h",
"base/chromeos/crosier/chromeos_test_launcher.cc",
Expand Down Expand Up @@ -1043,6 +1045,7 @@ if (is_chromeos && is_chromeos_device) {
"../browser/ash/external_metrics_integration_test.cc",
"../browser/ash/featured_integration_test.cc",
"../browser/ash/login/lock/lock_screen_integration_test.cc",
"../browser/ash/login/login_integration_test.cc",
"../browser/ash/ml_integration_test.cc",
"../browser/ash/screenshot_integration_test.cc",
"../browser/ui/ash/app_list/app_list_integration_test.cc",
Expand All @@ -1063,6 +1066,7 @@ if (is_chromeos && is_chromeos_device) {
}

deps += [
"//chrome/browser/ash:test_support",
"//chrome/test/base/chromeos/crosier:proto",
"//chrome/test/base/chromeos/crosier/helper:common",
"//chromeos/ash/components/standalone_browser",
Expand Down
230 changes: 230 additions & 0 deletions chrome/test/base/chromeos/crosier/chromeos_integration_login_mixin.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
// 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 "chrome/test/base/chromeos/crosier/chromeos_integration_login_mixin.h"

#include "ash/constants/ash_switches.h"
#include "base/threading/thread_restrictions.h"
#include "build/branding_buildflags.h"
#include "chrome/browser/ash/dbus/ash_dbus_helper.h"
#include "chrome/browser/ash/login/test/js_checker.h"
#include "chrome/browser/ash/login/test/oobe_screen_waiter.h"
#include "chrome/browser/ash/login/test/oobe_screens_utils.h"
#include "chrome/browser/ash/login/wizard_controller.h"
#include "chrome/browser/lifetime/application_lifetime.h"
#include "chrome/browser/ui/webui/ash/login/gaia_screen_handler.h"
#include "chrome/browser/ui/webui/signin/signin_utils.h"
#include "chrome/test/base/chromeos/crosier/test_accounts.h"
#include "chromeos/ash/components/dbus/session_manager/fake_session_manager_client.h"

namespace {

class FakeSessionManagerClientBrowserHelper
: public ash::DBusHelperObserverForTest {
public:
FakeSessionManagerClientBrowserHelper() {
ash::DBusHelperObserverForTest::Set(this);
}
FakeSessionManagerClientBrowserHelper(
const FakeSessionManagerClientBrowserHelper&) = delete;
FakeSessionManagerClientBrowserHelper& operator=(
const FakeSessionManagerClientBrowserHelper&) = delete;
~FakeSessionManagerClientBrowserHelper() override {
ash::DBusHelperObserverForTest::Set(nullptr);
}

// ash::DBusHelperObserverForTest:
void PostInitializeDBus() override {
// Create FakeSessionManageClient after real SessionManagerClient is created
// and before it is referenced.
scoped_fake_session_manager_client_.emplace();
ash::FakeSessionManagerClient::Get()->set_stop_session_callback(
base::BindOnce(&chrome::ExitIgnoreUnloadHandlers));
}

void PreShutdownDBus() override {
// Release FakeSessionManagerClient shutting down dbus clients.
scoped_fake_session_manager_client_.reset();
}

private:
// Optionally, use FakeSessionManagerClient if a test only needs the stub
// user session.
absl::optional<ash::ScopedFakeSessionManagerClient>
scoped_fake_session_manager_client_;
};

#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
content::RenderFrameHost* GetGaiaHost() {
constexpr char kGaiaFrameParentId[] = "signin-frame";
return signin::GetAuthFrame(
ash::LoginDisplayHost::default_host()->GetOobeWebContents(),
kGaiaFrameParentId);
}

ash::test::JSChecker GaiaFrameJS() {
return ash::test::JSChecker(GetGaiaHost());
}
#endif // BUILDFLAG(GOOGLE_CHROME_BRANDING)

} // namespace

ChromeOSIntegrationLoginMixin::ChromeOSIntegrationLoginMixin(
InProcessBrowserTestMixinHost* host)
: InProcessBrowserTestMixin(host) {}

ChromeOSIntegrationLoginMixin::~ChromeOSIntegrationLoginMixin() {
sudo_helper_client_.EnsureSessionManagerStopped();
}

void ChromeOSIntegrationLoginMixin::SetMode(Mode mode) {
CHECK(!setup_called_);
mode_ = mode;
}

void ChromeOSIntegrationLoginMixin::Login() {
switch (mode_) {
case Mode::kStubLogin: {
// Nothing to do since stub user should be signed in on start.
break;
}
case Mode::kTestLogin: {
DoTestLogin();
break;
}
case Mode::kGaiaLogin: {
DoGaiaLogin();
break;
}
}
}

void ChromeOSIntegrationLoginMixin::SetUp() {
setup_called_ = true;

switch (mode_) {
case Mode::kStubLogin: {
fake_session_manager_client_helper_ =
std::make_unique<FakeSessionManagerClientBrowserHelper>();
break;
}
case Mode::kTestLogin: {
[[fallthrough]];
}
case Mode::kGaiaLogin: {
PrepareForNewUserLogin();
break;
}
}
}

void ChromeOSIntegrationLoginMixin::SetUpCommandLine(
base::CommandLine* command_line) {
command_line->AppendSwitch(
ash::switches::kDisableHIDDetectionOnOOBEForTesting);

if (mode_ != Mode::kGaiaLogin) {
command_line->AppendSwitch(ash::switches::kDisableGaiaServices);
}

if (ShouldStartLoginScreen()) {
command_line->AppendSwitch(ash::switches::kLoginManager);
command_line->AppendSwitch(ash::switches::kForceLoginManagerInTests);
command_line->AppendSwitch(
ash::switches::kDisableOOBEChromeVoxHintTimerForTesting);
command_line->AppendSwitchASCII(ash::switches::kLoginProfile, "user");
}

if (mode_ == Mode::kTestLogin) {
// Needed to use Oobe test api to login.
command_line->AppendSwitch(ash::switches::kEnableOobeTestAPI);
}
}

bool ChromeOSIntegrationLoginMixin::ShouldStartLoginScreen() const {
return mode_ == Mode::kTestLogin || mode_ == Mode::kGaiaLogin;
}

void ChromeOSIntegrationLoginMixin::PrepareForNewUserLogin() {
CHECK(sudo_helper_client_.RunCommand("./reset_dut.py").return_code == 0);

// Starts session_manager daemon and use `chrome::ExitIgnoreUnloadHandlers` as
// the session_manager stopped callback. The callback would be invoked
// when session_manager daemon terminates (getting a StopSession dbus call,
// or crashed).
//
// In normal case, SessionManagerClient::StopSession would be called when a
// test case finishes. The daemon would terminate and `test_sudo_helper.py`
// script sends a message back to `TestSudoHelperClient`, which triggers the
// callback.
//
// For the crashed case, the callback would be invoked before the test case
// finishes and cause it to fail.
auto result = sudo_helper_client_.StartSessionManager(
base::BindOnce(&chrome::ExitIgnoreUnloadHandlers));
CHECK_EQ(result.return_code, 0);
}

void ChromeOSIntegrationLoginMixin::DoTestLogin() {
ash::test::WaitForOobeJSReady();

// Any gmail account and password works for test login.
constexpr char kTestUser[] = "testuser@gmail.com";
constexpr char kTestPassword[] = "testpass";
constexpr char kTestGaiaId[] = "12345";

ash::test::OobeJS().Evaluate(
base::StringPrintf("Oobe.loginForTesting(\"%s\", \"%s\",\"%s\")",
kTestUser, kTestPassword, kTestGaiaId));

// Skip post login steps, such as ToS etc.
ash::WizardController::default_controller()->SkipPostLoginScreensForTesting();
}

void ChromeOSIntegrationLoginMixin::DoGaiaLogin() {
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
// Skip to login screen.
ash::WizardController::default_controller()->SkipToLoginForTesting();
ash::OobeScreenWaiter(ash::GaiaView::kScreenId).Wait();

// Wait for Gaia page to load.
while (!GetGaiaHost()) {
base::RunLoop run_loop;
base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE, run_loop.QuitClosure(), base::Milliseconds(500));
run_loop.Run();
}

std::string email;
std::string password;

{
// Allows reading account pool json file.
base::ScopedAllowBlockingForTesting allow_blocking;

crosier::GetGaiaTestAccount(email, password);
CHECK(!email.empty() && !password.empty());
}

GaiaFrameJS()
.CreateWaiter("!!document.querySelector('#identifierId')")
->Wait();
GaiaFrameJS().Evaluate(base::StrCat(
{"document.querySelector('#identifierId').value=\"", email, "\""}));
ash::test::OobeJS().Evaluate("Oobe.clickGaiaPrimaryButtonForTesting()");

GaiaFrameJS()
.CreateWaiter("!!document.querySelector('input[type=password]')")
->Wait();
GaiaFrameJS().Evaluate(
base::StrCat({"document.querySelector('input[type=password]').value=\"",
password, "\""}));
ash::test::OobeJS().Evaluate("Oobe.clickGaiaPrimaryButtonForTesting()");

// Skip post login steps, such as ToS etc.
ash::WizardController::default_controller()->SkipPostLoginScreensForTesting();
#else
CHECK(false) << "Gaia login is only supported in branded build.";
#endif // BUILDFLAG(GOOGLE_CHROME_BRANDING)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// 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.

#ifndef CHROME_TEST_BASE_CHROMEOS_CROSIER_CHROMEOS_INTEGRATION_LOGIN_MIXIN_H_
#define CHROME_TEST_BASE_CHROMEOS_CROSIER_CHROMEOS_INTEGRATION_LOGIN_MIXIN_H_

#include <memory>
#include <string>

#include "chrome/test/base/chromeos/crosier/helper/test_sudo_helper_client.h"
#include "chrome/test/base/mixin_based_in_process_browser_test.h"

namespace {
class FakeSessionManagerClientBrowserHelper;
}

// Provides login supports to ChromeOS integration tests.
class ChromeOSIntegrationLoginMixin : public InProcessBrowserTestMixin {
public:
enum class Mode {
// Mode starts from the chrome restart code path. It skips the login screen
// and starts a stub user session automatically. It does not run session
// manager daemon so it will not notify system daemons about user sign-in
// state. It is intended for tests that do not care about login and only
// test states in chrome.
kStubLogin,

// Mode starts from the login screen and uses test api to login. It starts
// session_manager daemon and does cryptohome mount like production chrome.
// Tests that do not depend on gaia identity should use this mode.
kTestLogin,

// Mode is the same as `kTestLogin` except that it uses the production gaia
// server to authenticate. Tests that need gaia identity should use this
// mode.
kGaiaLogin,
};

explicit ChromeOSIntegrationLoginMixin(InProcessBrowserTestMixinHost* host);
ChromeOSIntegrationLoginMixin(const ChromeOSIntegrationLoginMixin&) = delete;
ChromeOSIntegrationLoginMixin& operator=(
const ChromeOSIntegrationLoginMixin&) = delete;
~ChromeOSIntegrationLoginMixin() override;

// Set the login mode. Must be called before SetUp.
void SetMode(Mode mode);

// Signs in a test account. For kTestLogin, it is a fake testuser@gmail.com.
// For kGaiaLogin, the account is randomly picked from `gaiaPoolDefault`.
void Login();

// InProcessBrowserTestMixin:
void SetUp() override;
void SetUpCommandLine(base::CommandLine* command_line) override;

private:
bool ShouldStartLoginScreen() const;
void PrepareForNewUserLogin();

// Performs login for kTestLogin mode by using Oobe test api.
void DoTestLogin();

// Performs login for kGaiaLogin mode by authenticating through production
// gaia server.
void DoGaiaLogin();

bool setup_called_ = false;

Mode mode_ = Mode::kStubLogin;

std::unique_ptr<FakeSessionManagerClientBrowserHelper>
fake_session_manager_client_helper_;
TestSudoHelperClient sudo_helper_client_;
};

#endif // CHROME_TEST_BASE_CHROMEOS_CROSIER_CHROMEOS_INTEGRATION_LOGIN_MIXIN_H_

0 comments on commit 4ca35db

Please sign in to comment.