blob: 60c9c48b29b4d5b9650fb816e53a34334d349f12 [file] [log] [blame]
// Copyright 2022 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package lacros
import (
"context"
"os"
"chromiumos/tast/errors"
"chromiumos/tast/local/chrome"
"chromiumos/tast/local/chrome/browser"
"chromiumos/tast/local/chrome/lacros"
"chromiumos/tast/local/chrome/localstate"
"chromiumos/tast/local/chrome/uiauto"
"chromiumos/tast/local/chrome/uiauto/nodewith"
"chromiumos/tast/local/chrome/uiauto/role"
"chromiumos/tast/local/cryptohome"
"chromiumos/tast/local/input"
"chromiumos/tast/local/policyutil"
"chromiumos/tast/testing"
)
func init() {
testing.AddTest(&testing.Test{
Func: Migrate,
LacrosStatus: testing.LacrosVariantExists,
Desc: "Test basic functionality of Ash-to-Lacros profile migration",
Contacts: []string{
"neis@google.com", // Test author
"ythjkt@google.com",
"hidehiko@google.com",
"lacros-team@google.com",
},
Attr: []string{"group:mainline", "informational"},
SoftwareDeps: []string{"chrome", "lacros"},
Vars: []string{"lacrosDeployedBinary"},
Params: []testing.Param{{
Name: "copy",
Val: []chrome.Option{chrome.DisableFeatures("LacrosMoveProfileMigration")},
}, {
Name: "move",
Val: []chrome.Option{chrome.EnableFeatures("LacrosMoveProfileMigration")},
}},
})
}
func Migrate(ctx context.Context, s *testing.State) {
kb, err := input.Keyboard(ctx)
if err != nil {
s.Fatal("Failed to get keyboard: ", err)
}
defer kb.Close()
prepareAshProfile(ctx, s, kb)
cr, err := migrateProfile(ctx, s.Param().([]chrome.Option))
if err != nil {
s.Fatal("Failed to migrate profile: ", err)
}
defer cr.Close(ctx)
verifyLacrosProfile(ctx, s, kb, cr)
}
const (
bookmarkName = "MyBookmark12345" // Arbitrary.
extensionName = "User-Agent Switcher for Chrome" // Arbitrary extension from Chrome Store.
extensionID = "djflhoibgkdhkhhcedjiklpkjnoahfmg" // ID of the above extension.
shortcutName = "MyShortcut12345" // Arbitrary.
titleOfDownloadsPage = "Downloads" // chrome://downloads page title.
)
// prepareAshProfile resets profile migration and creates two tabs, a bookmark, a download, and a shortcut.
func prepareAshProfile(ctx context.Context, s *testing.State, kb *input.KeyboardEventWriter) {
// First restart Chrome with Lacros disabled in order to reset profile migration.
cr, err := chrome.New(ctx, chrome.DisableFeatures("LacrosSupport"))
if err != nil {
s.Fatal("Failed to start Chrome: ", err)
}
defer cr.Close(ctx)
if err := testing.Poll(ctx, func(ctx context.Context) error {
if _, err := os.Stat("/home/chronos/user/lacros/First Run"); !os.IsNotExist(err) {
return errors.Wrap(err, "'First Run' file exists or cannot be read")
}
return nil
}, nil); err != nil {
s.Fatal("'First Run' file exists or cannot be read: ", err)
}
tconn, err := cr.TestAPIConn(ctx)
if err != nil {
s.Fatal("Failed to create Test API connection: ", err)
}
ui := uiauto.New(tconn)
// Install an extension.
if err := policyutil.EnsureGoogleCookiesAccepted(ctx, cr.Browser()); err != nil {
s.Fatal("Failed to accept cookies: ", err)
}
extensionURL := "https://chrome.google.com/webstore/detail/" + extensionID + "?hl=en"
conn, err := cr.NewConn(ctx, extensionURL)
if err != nil {
s.Fatal("Failed to open extension page: ", err)
}
defer conn.Close()
addButton1 := nodewith.Name("Add to Chrome").Role(role.Button).First()
addButton2 := nodewith.Name("Add extension").Role(role.Button)
removeButton := nodewith.Name("Remove from Chrome").Role(role.Button).First()
if err := uiauto.Combine("Install extension",
ui.LeftClick(addButton1),
// The "Add extension" button may not immediately be clickable.
ui.LeftClickUntil(addButton2, ui.Gone(addButton2)),
ui.WaitUntilExists(removeButton),
)(ctx); err != nil {
s.Fatal("Failed to install: ", err)
}
// Bookmark the chrome://downloads page.
if err := conn.Navigate(ctx, "chrome://downloads"); err != nil {
s.Fatal("Failed to open downloads page: ", err)
}
if err := kb.Accel(ctx, "Ctrl+d"); err != nil {
s.Fatal("Failed to open bookmark creation popup: ", err)
}
if err := kb.Type(ctx, bookmarkName); err != nil {
s.Fatal("Failed to type bookmark name: ", err)
}
doneButton := nodewith.Name("Done").Role(role.Button)
if err := uiauto.Combine("Click 'Done' button",
ui.LeftClick(doneButton),
ui.WaitUntilGone(doneButton),
)(ctx); err != nil {
s.Fatal("Failed to click: ", err)
}
// Also download that page.
if err := kb.Accel(ctx, "Ctrl+s"); err != nil {
s.Fatal("Failed to open download popup: ", err)
}
saveButton := nodewith.Name("Save").Role(role.Button)
if err := uiauto.Combine("Click 'Save' button",
ui.LeftClick(saveButton),
ui.WaitUntilGone(saveButton),
)(ctx); err != nil {
s.Fatal("Failed to click: ", err)
}
// Create a shortcut on the newtab page.
if err := kb.Accel(ctx, "Ctrl+t"); err != nil {
s.Fatal("Failed to open new tab: ", err)
}
addShortcutButton := nodewith.Name("Add shortcut").Role(role.Button)
if err := uiauto.Combine("Click 'Add shortcut' button",
ui.LeftClick(addShortcutButton),
ui.WaitUntilGone(addShortcutButton),
)(ctx); err != nil {
s.Fatal("Failed to click: ", err)
}
if err := kb.Type(ctx, shortcutName+"\tfoobar"); err != nil {
s.Fatal("Failed to type shortcut data: ", err)
}
if err := uiauto.Combine("Click 'Done' button",
ui.LeftClick(doneButton),
ui.WaitUntilGone(doneButton),
)(ctx); err != nil {
s.Fatal("Failed to click: ", err)
}
}
// verifyLacrosProfile checks that the edits done by prepareAshProfile were carried over to Lacros.
func verifyLacrosProfile(ctx context.Context, s *testing.State, kb *input.KeyboardEventWriter, cr *chrome.Chrome) {
if _, err := os.Stat("/home/chronos/user/lacros/First Run"); err != nil {
s.Fatal("Error reading 'First Run' file: ", err)
}
tconn, err := cr.TestAPIConn(ctx)
if err != nil {
s.Fatal("Failed to create Test API connection: ", err)
}
l, err := lacros.Launch(ctx, tconn)
if err != nil {
s.Fatal("Failed to launch lacros: ", err)
}
// Check that the bookmark is present.
ui := uiauto.New(tconn)
bookmarkedButton := nodewith.Name(bookmarkName).Role(role.Button)
if err = ui.WaitUntilExists(bookmarkedButton)(ctx); err != nil {
s.Error("Failed to find bookmark: ", err)
}
// Check that the shortcut is present.
shortcutLink := nodewith.Name(shortcutName).Role(role.Link)
if err := ui.WaitUntilExists(shortcutLink)(ctx); err != nil {
s.Error("Failed to find shortcut: ", err)
}
// Check that there is another tab showing the downloads page.
if err := kb.Accel(ctx, "Ctrl+w"); err != nil {
s.Fatal("Failed to close tab: ", err)
}
if err := lacros.WaitForLacrosWindow(ctx, tconn, titleOfDownloadsPage); err != nil {
s.Fatal("Failed to find appropriate window: ", err)
}
// Check that the download page shows the previous download (of itself).
downloadedFile := nodewith.Name(titleOfDownloadsPage + ".mhtml").Role(role.Link)
if err = ui.WaitUntilExists(downloadedFile)(ctx); err != nil {
s.Error("Failed to find download: ", err)
}
// Check that the extension is installed and enabled.
conn, err := l.NewConn(ctx, "chrome://extensions/?id="+extensionID)
if err != nil {
s.Fatal("Failed to open extension page: ", err)
}
defer conn.Close()
extensionText := nodewith.Name(extensionName).Role(role.StaticText)
onText := nodewith.Name("On").Role(role.StaticText)
if err := uiauto.Combine("Verify extension status",
ui.WaitUntilExists(extensionText),
ui.Exists(onText),
)(ctx); err != nil {
s.Error("Failed: ", err)
}
}
func migrateProfile(ctx context.Context, extraOpts []chrome.Option) (*chrome.Chrome, error) {
// TODO(chromium:1290297): This is a hack.
// chrome.New doesn't really support profile migration because it
// doesn't anticipate the additional Chrome restart that profile
// migration effects. As a result, the *Chrome return value is already
// invalid and we must not use it. Moreover, we must disable the
// RemoveNotification option because otherwise chrome.New will try to
// interact with Chrome at a time when that is no longer safe.
// In order to obtain a valid *Chrome value for the test to continue
// with, we restart Chrome once more after profile migration.
testing.ContextLog(ctx, "Restarting for profile migration")
opts := []chrome.Option{
chrome.KeepState(),
chrome.EnableFeatures("LacrosSupport", "LacrosPrimary", "LacrosProfileMigrationForAnyUser"),
// Disable keep-alive and login-lacros-opening to work around chromium:1316237.
chrome.ExtraArgs("--lacros-selection=rootfs", "--disable-lacros-keep-alive", "--disable-login-lacros-opening"),
chrome.LacrosExtraArgs("--remote-debugging-port=0"),
chrome.RemoveNotification(false),
}
opts = append(opts, extraOpts...)
crDoNotUse, err := chrome.New(ctx, opts...)
if err != nil {
return nil, err
}
defer crDoNotUse.Close(ctx)
testing.ContextLog(ctx, "Waiting for profile migration to complete")
userHash, err := cryptohome.UserHash(ctx, chrome.DefaultUser)
if err != nil {
return nil, err
}
pref := "lacros.profile_migration_completed_for_user." + userHash
if err := testing.Poll(ctx, func(ctx context.Context) error {
completedVal, err := localstate.UnmarshalPref(browser.TypeAsh, pref)
if err != nil {
return err
}
completed, ok := completedVal.(bool)
if !ok || !completed {
return errors.New("profile migration incomplete")
}
return nil
}, nil); err != nil {
return nil, err
}
testing.ContextLog(ctx, "Restarting after profile migration")
return chrome.New(ctx, opts...)
}