blob: 4a31cad895eb13efdfe24e4a9ecc479e55907859 [file] [log] [blame]
// Copyright 2021 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 arc
import (
"context"
"time"
"chromiumos/tast/ctxutil"
"chromiumos/tast/errors"
"chromiumos/tast/local/apps"
"chromiumos/tast/local/arc"
"chromiumos/tast/local/arc/optin"
"chromiumos/tast/local/chrome"
"chromiumos/tast/local/chrome/ash"
"chromiumos/tast/local/chrome/uiauto"
"chromiumos/tast/local/chrome/uiauto/faillog"
"chromiumos/tast/local/chrome/uiauto/nodewith"
"chromiumos/tast/local/chrome/uiauto/ossettings"
"chromiumos/tast/local/chrome/uiauto/role"
"chromiumos/tast/local/upstart"
"chromiumos/tast/testing"
)
const ghostWindowPlayStorePkgName = "com.android.vending"
const ghostWindowARCSettingsPkgName = "com.android.settings"
func init() {
testing.AddTest(&testing.Test{
Func: GhostWindow,
LacrosStatus: testing.LacrosVariantUnneeded,
Desc: "Test ghost window for ARC Apps",
Contacts: []string{"sstan@google.com", "arc-framework+tast@google.com"},
Attr: []string{"group:mainline", "informational"},
SoftwareDeps: []string{"chrome", "arc"},
Timeout: 5 * time.Minute,
Vars: []string{"ui.gaiaPoolDefault"},
})
}
func waitARCWindowShown(ctx context.Context, tconn *chrome.TestConn, timeout time.Duration, pkgName string) error {
return testing.Poll(ctx, func(ctx context.Context) error {
if _, err := ash.GetARCAppWindowInfo(ctx, tconn, pkgName); err != nil {
return err
}
return nil
}, &testing.PollOptions{Timeout: timeout})
}
func waitGhostWindowShown(ctx context.Context, tconn *chrome.TestConn, timeout time.Duration, appID string) error {
return testing.Poll(ctx, func(ctx context.Context) error {
if _, err := ash.GetARCGhostWindowInfo(ctx, tconn, appID); err != nil {
return err
}
return nil
}, &testing.PollOptions{Timeout: timeout})
}
func loginChrome(ctx context.Context, s *testing.State, creds *chrome.Creds) (*chrome.Chrome, error) {
if creds != nil {
// Setup Chrome. Login by the creds.
cr, err := chrome.New(ctx,
chrome.GAIALogin(*creds),
chrome.ARCSupported(),
chrome.EnableFeatures("FullRestore"),
chrome.EnableFeatures("ArcGhostWindow"),
chrome.RemoveNotification(false),
chrome.KeepState(),
chrome.ExtraArgs(arc.DisableSyncFlags()...))
if err != nil {
return nil, errors.Wrap(err, "failed to start Chrome")
}
return cr, nil
}
// Setup Chrome for a new cred.
cr, err := chrome.New(ctx,
chrome.GAIALoginPool(s.RequiredVar("ui.gaiaPoolDefault")),
chrome.ARCSupported(),
chrome.EnableFeatures("FullRestore"),
chrome.EnableFeatures("ArcGhostWindow"),
chrome.ExtraArgs(arc.DisableSyncFlags()...))
if err != nil {
return nil, errors.Wrap(err, "failed to start Chrome")
}
return cr, nil
}
func clickRestoreButtonNormalStatus(ctx context.Context, cr *chrome.Chrome, tconn *chrome.TestConn, s *testing.State) error {
alertDialog := nodewith.NameStartingWith("Restore apps?").Role(role.AlertDialog)
restoreButton := nodewith.Name("RESTORE").Role(role.Button).Ancestor(alertDialog)
ui := uiauto.New(tconn)
defer faillog.DumpUITreeWithScreenshotOnError(ctx, s.OutDir(), s.HasError, cr, "click_normal_restore")
if err := uiauto.Combine("restore playstore",
// Click Restore on the restore alert.
ui.LeftClick(restoreButton))(ctx); err != nil {
return err
}
return nil
}
func clickRestoreButtonCrashedStatus(ctx context.Context, cr *chrome.Chrome, tconn *chrome.TestConn, s *testing.State) error {
// Full text is "Your *Chromebook* restarted unexpectedly".
alertDialog := nodewith.NameStartingWith("Your").Role(role.AlertDialog)
restoreButton := nodewith.Name("RESTORE").Role(role.Button).Ancestor(alertDialog)
ui := uiauto.New(tconn)
defer faillog.DumpUITreeWithScreenshotOnError(ctx, s.OutDir(), s.HasError, cr, "click_crash_restore")
if err := uiauto.Combine("restore playstore",
// Click Restore on the restore alert.
ui.LeftClick(restoreButton))(ctx); err != nil {
return err
}
return nil
}
func waitForWindowInfoSaved(ctx context.Context) {
// According to the PRD of Full Restore go/chrome-os-full-restore-dd,
// it uses a throttle of 2.5s to save the app launching and window status
// information to the backend. Therefore, sleep 5 seconds here.
testing.Sleep(ctx, 5*time.Second)
}
func optinAndLaunchPlayStore(ctx context.Context, s *testing.State, cr *chrome.Chrome) error {
// Optin to Play Store.
s.Log("Opting into Play Store")
maxAttempts := 1
if err := optin.PerformWithRetry(ctx, cr, maxAttempts); err != nil {
return errors.Wrap(err, "failed to optin to Play Store")
}
tconn, err := cr.TestAPIConn(ctx)
if err != nil {
return errors.Wrap(err, "failed to create test API connection")
}
// The PlayStore only popup automatically on first optin of an account.
// Launch it here in case it's not the first optin.
if err := apps.Launch(ctx, tconn, apps.PlayStore.ID); err != nil {
return errors.Wrap(err, "failed to launch Play Store")
}
// In this case we cannot use this func, since it inspect App by check shelf ID.
// After ghost window finish ash shelf integration, the ghost window will also
// carry the corresponding app's ID into shelf. Here we need to check actual
// aura window.
if err := waitARCWindowShown(ctx, tconn, time.Minute, ghostWindowPlayStorePkgName); err != nil {
return errors.Wrap(err, "failed to wait for Play Store")
}
return nil
}
// launchAndroidSettings opens the ARC Settings Page from Chrome Settings.
func launchAndroidSettings(ctx context.Context, cr *chrome.Chrome, tconn *chrome.TestConn) error {
ui := uiauto.New(tconn)
playStoreButton := nodewith.Name("Google Play Store").Role(role.Button)
settingPage, err := ossettings.LaunchAtPageURL(ctx, tconn, cr, "apps", ui.Exists(playStoreButton))
if err != nil {
return errors.Wrap(err, "failed to launch apps settings page")
}
if err := uiauto.Combine("open Android settings",
ui.FocusAndWait(playStoreButton),
ui.LeftClick(playStoreButton),
ui.LeftClick(nodewith.Name("Manage Android preferences").Role(role.Link)),
)(ctx); err != nil {
return errors.Wrap(err, "failed to open ARC settings page")
}
if err := waitARCWindowShown(ctx, tconn, 10*time.Second, ghostWindowARCSettingsPkgName); err != nil {
return errors.Wrapf(err, "failed to wait ARC window %s shown", ghostWindowARCSettingsPkgName)
}
// Close ChromeOS setting page to avoid affect window restore.
return settingPage.Close(ctx)
}
func verifyGhostWindow(ctx context.Context, s *testing.State, cr *chrome.Chrome, isCrash bool, appID string) error {
tconn, err := cr.TestAPIConn(ctx)
if err != nil {
return errors.Wrap(err, "failed to create test API connection")
}
if isCrash {
if err := clickRestoreButtonCrashedStatus(ctx, cr, tconn, s); err != nil {
return errors.Wrap(err, "failed to click Restore button on crash restore notification")
}
} else {
if err := clickRestoreButtonNormalStatus(ctx, cr, tconn, s); err != nil {
return errors.Wrap(err, "failed to click Restore button on normal restore notification")
}
}
// Make sure ARC Ghost Window of PlayStore has popup.
if err := waitGhostWindowShown(ctx, tconn, time.Minute, appID); err != nil {
return errors.Wrap(err, "failed to wait for Play Store")
}
return nil
}
func GhostWindow(ctx context.Context, s *testing.State) {
// Reserve 10 seconds for clean-up tasks.
cleanupCtx := ctx
ctx, cancel := ctxutil.Shorten(ctx, 10*time.Second)
defer cancel()
// Test restore single PlayStore task.
{
// Test ghost window in logout case.
cr, err := loginChrome(ctx, s, nil)
if err != nil {
s.Fatal("Failed to optin: ", err)
}
defer cr.Close(cleanupCtx)
creds := cr.Creds()
if err := optinAndLaunchPlayStore(ctx, s, cr); err != nil {
s.Fatal("Failed to initial optin: ", err)
}
// Stop Chrome after window info saved.
waitForWindowInfoSaved(ctx)
if err := upstart.RestartJob(ctx, "ui"); err != nil {
s.Fatal("Failed to log out: ", err)
}
// Re-login.
cr, err = loginChrome(ctx, s, &creds)
if err != nil {
s.Fatal("Failed to re-optin: ", err)
}
defer cr.Close(cleanupCtx)
if err := verifyGhostWindow(ctx, s, cr, false, apps.PlayStore.ID); err != nil {
s.Fatal("Failed to launch ghost window: ", err)
}
}
// Test restore PlayStore and Android Setting tasks.
{
// Test ghost window in logout case.
cr, err := loginChrome(ctx, s, nil)
if err != nil {
s.Fatal("Failed to optin: ", err)
}
defer cr.Close(cleanupCtx)
creds := cr.Creds()
if err := optinAndLaunchPlayStore(ctx, s, cr); err != nil {
s.Fatal("Failed to initial optin: ", err)
}
tconn, err := cr.TestAPIConn(ctx)
if err != nil {
s.Fatal("Failed to create Test API connection: ", err)
}
if err := launchAndroidSettings(ctx, cr, tconn); err != nil {
s.Fatal("Failed to launch ARC setting: ", err)
}
// Stop Chrome after window info saved.
waitForWindowInfoSaved(ctx)
if err := upstart.RestartJob(ctx, "ui"); err != nil {
s.Fatal("Failed to log out: ", err)
}
// Re-login
cr, err = loginChrome(ctx, s, &creds)
if err != nil {
s.Fatal("Failed to re-optin: ", err)
}
defer cr.Close(cleanupCtx)
if err := verifyGhostWindow(ctx, s, cr, false, apps.AndroidSettings.ID); err != nil {
s.Fatal("Failed to launch ghost window: ", err)
}
}
}