blob: b29ea1c10b5caf13bd19c24003180d7920c0508c [file] [log] [blame]
// Copyright 2020 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 ui
import (
"context"
"time"
"chromiumos/tast/errors"
"chromiumos/tast/local/bundles/cros/ui/perfutil"
"chromiumos/tast/local/chrome"
"chromiumos/tast/local/chrome/ash"
"chromiumos/tast/local/chrome/display"
"chromiumos/tast/local/chrome/ui/mouse"
"chromiumos/tast/local/chrome/uiauto"
"chromiumos/tast/local/chrome/uiauto/faillog"
"chromiumos/tast/local/chrome/uiauto/nodewith"
"chromiumos/tast/local/coords"
"chromiumos/tast/local/input"
"chromiumos/tast/testing"
"chromiumos/tast/testing/hwdep"
)
func init() {
testing.AddTest(&testing.Test{
Func: DesktopControl,
Desc: "Check if the performance around desktop UI components is good enough; see also go/cros-ui-perftests-cq#heading=h.fwfk0yg3teo1",
Contacts: []string{
"newcomer@chromium.org",
"tbarzic@chromium.org",
"kaznacheev@chromium.org",
"mukai@chromium.org", // Tast author
},
Attr: []string{"group:mainline"},
Fixture: "chromeLoggedIn",
SoftwareDeps: []string{"chrome"},
HardwareDeps: hwdep.D(hwdep.InternalDisplay()),
Params: []testing.Param{
{
ExtraHardwareDeps: hwdep.D(hwdep.SkipOnModel(perfutil.UnstableModels...)),
},
// TODO(crbug.com/1163981): remove "unstable" once we see stability on all platforms.
{
Name: "unstable",
ExtraAttr: []string{"informational"},
ExtraHardwareDeps: hwdep.D(hwdep.Model(perfutil.UnstableModels...)),
},
},
})
}
func DesktopControl(ctx context.Context, s *testing.State) {
expects := perfutil.CreateExpectations(ctx,
"Apps.StateTransition.AnimationSmoothness.Peeking.ClamshellMode",
"Apps.StateTransition.AnimationSmoothness.Half.ClamshellMode",
"Apps.StateTransition.AnimationSmoothness.FullscreenSearch.ClamshellMode",
"Apps.StateTransition.AnimationSmoothness.FullscreenAllApps.ClamshellMode",
"Apps.StateTransition.AnimationSmoothness.Close.ClamshellMode",
"Apps.StateTransition.Drag.PresentationTime",
"ChromeOS.SystemTray.AnimationSmoothness.TransitionToCollapsed",
"ChromeOS.SystemTray.AnimationSmoothness.TransitionToExpanded",
)
// When custom expectation value needs to be set, modify expects here.
cr := s.FixtValue().(*chrome.Chrome)
tconn, err := cr.TestAPIConn(ctx)
if err != nil {
s.Fatal("Failed to get the connection to the test API: ", err)
}
cleanup, err := ash.EnsureTabletModeEnabled(ctx, tconn, false)
if err != nil {
s.Fatal("Failed to ensure into the clamshell mode: ", err)
}
defer cleanup(ctx)
defer faillog.DumpUITreeOnError(ctx, s.OutDir(), s.HasError, tconn)
if err := ash.CreateWindows(ctx, tconn, cr, "", 2); err != nil {
s.Fatal("Failed to create new windows: ", err)
}
// This test assumes shelf visibility, setting the shelf behavior explicitly.
info, err := display.GetPrimaryInfo(ctx, tconn)
if err != nil {
s.Fatal("Failed to find the primary display info: ", err)
}
shelfBehavior, err := ash.GetShelfBehavior(ctx, tconn, info.ID)
if err != nil {
s.Fatal("Failed to get the shelf behavior for display ID ", info.ID)
}
if shelfBehavior != ash.ShelfBehaviorNeverAutoHide {
if err := ash.SetShelfBehavior(ctx, tconn, info.ID, ash.ShelfBehaviorNeverAutoHide); err != nil {
s.Fatal("Failed to sete the shelf behavior to 'never auto-hide' for display ID ", info.ID)
}
}
// Turn all windows into normal state.
if err := ash.ForEachWindow(ctx, tconn, func(w *ash.Window) error {
return ash.SetWindowStateAndWait(ctx, tconn, w.ID, ash.WindowStateNormal)
}); err != nil {
s.Fatal("Failed to set all windows as normal state: ", err)
}
r := perfutil.NewRunner(cr)
r.Runs = 3
r.RunTracing = false
kw, err := input.Keyboard(ctx)
if err != nil {
s.Fatal("Failed to take keyboard: ", err)
}
// Controls of the launcher:
// - hit the search key to open the launcher as the peeking state
// - type a query string to turn the launcher into half state
// - hit shift+search to turn the launcher into the fullscreen
// - hit esc key to turn show the app list in fullscreen
// - hit esc key to close the launcher
s.Log("Open/close the launcher")
r.RunMultiple(ctx, s, "launcher", perfutil.RunAndWaitAll(tconn, func(ctx context.Context) error {
for i, action := range []struct {
key string
isAccel bool
targetState ash.LauncherState
}{
{"Search", true, ash.Peeking}, // Search key to open the launcher as peeking
{"lorem ipsum", false, ash.Half}, // Type some query
{"Shift+Search", true, ash.FullscreenSearch}, // Shift+Search to turn into fullsceen
{"Esc", true, ash.FullscreenAllApps}, // Esc to cancel the query
{"Esc", true, ash.Closed}, // Esc to close.
} {
if action.isAccel {
if err := kw.Accel(ctx, action.key); err != nil {
return errors.Wrapf(err, "failed to type key %q at step %d", action.key, i)
}
} else {
if err := kw.Type(ctx, action.key); err != nil {
return errors.Wrapf(err, "failed to type %q at step %d", action.key, i)
}
}
if err := ash.WaitForLauncherState(ctx, tconn, action.targetState); err != nil {
return errors.Wrapf(err, "the launcher isn't in the state %q at step %d", action.targetState, i)
}
}
return nil
},
"Apps.StateTransition.AnimationSmoothness.Peeking.ClamshellMode",
"Apps.StateTransition.AnimationSmoothness.Half.ClamshellMode",
"Apps.StateTransition.AnimationSmoothness.FullscreenSearch.ClamshellMode",
"Apps.StateTransition.AnimationSmoothness.FullscreenAllApps.ClamshellMode",
"Apps.StateTransition.AnimationSmoothness.Close.ClamshellMode",
), perfutil.StoreAllWithHeuristics(""))
// Controls of the launcher by mouse drag:
// - drag up from the bottom of the screen to open the launcher
// - drag down from the top of the screen to close the launcher
s.Log("Open/close the launcher by drag")
dragStart := coords.NewPoint(info.Bounds.Left+info.Bounds.Width/4, info.Bounds.Bottom()-1)
dragEnd := coords.NewPoint(dragStart.X, info.Bounds.Top+1)
r.RunMultiple(ctx, s, "launcher-drag", perfutil.RunAndWaitAll(tconn, func(ctx context.Context) error {
if err := mouse.Drag(ctx, tconn, dragStart, dragEnd, time.Second); err != nil {
return errors.Wrap(err, "failed to drag to open the launcher")
}
if err := ash.WaitForLauncherState(ctx, tconn, ash.FullscreenAllApps); err != nil {
return errors.Wrap(err, "launcher isn't in fullscreen")
}
if err := mouse.Drag(ctx, tconn, dragEnd, dragStart, time.Second); err != nil {
return errors.Wrap(err, "failed to drag to close the launcher")
}
if err := ash.WaitForLauncherState(ctx, tconn, ash.Closed); err != nil {
return errors.Wrap(err, "launcher isn't closed")
}
return nil
},
"Apps.StateTransition.Drag.PresentationTime.ClamshellMode",
), perfutil.StoreAllWithHeuristics(""))
// Controls of the quick settings:
// - open the quick settings
// - click the collapse button to shrink the quick settings
// - click the collapse button again to expand the quick settings
// - close the quick settings
s.Log("Shrink and expand the quick settings")
r.RunMultiple(ctx, s, "quick-settings", perfutil.RunAndWaitAll(tconn, func(ctx context.Context) error {
// The option to be used for StableLeftClick and WaitLocationStable. Also,
// it uses a longer interval (default interval is 100msecs), as the location
// update may not happen very quickly.
waitingOption := testing.PollOptions{Timeout: 10 * time.Second, Interval: time.Second}
uiBase := uiauto.New(tconn)
ui := uiBase.WithPollOpts(waitingOption)
statusarea := nodewith.ClassName("ash/StatusAreaWidgetDelegate")
collapseButton := nodewith.ClassName("CollapseButton")
if err := uiauto.Combine(
"quick-settings open and close",
// Seems that the quick settings open/close don't have metrics, but in
// case that's added in the future, it is noted here.
ui.WaitUntilExists(statusarea),
ui.LeftClick(statusarea),
// Wait for the collapse button to exist, for 20 seconds.
uiBase.WithTimeout(20*time.Second).WaitUntilExists(collapseButton),
// Click the collapse button to shrink.
ui.LeftClick(collapseButton),
// Click the collapse button to expand.
ui.LeftClick(collapseButton),
// Wait for the quick settings to be fully expanded.
ui.WaitForLocation(collapseButton),
// Close the quick settings by clicking the status area again.
ui.LeftClick(statusarea))(ctx); err != nil {
return errors.Wrap(err, "failed to proceed the test scenario")
}
// Right now there's no way to identify the quick settings is closed.
// Just wait 1 second. TODO(crbug.com/1099502): replace by a test API.
if err := testing.Sleep(ctx, time.Second); err != nil {
return errors.Wrap(err, "failed to wait for the quick settings to be closed")
}
return nil
},
"ChromeOS.SystemTray.AnimationSmoothness.TransitionToCollapsed",
"ChromeOS.SystemTray.AnimationSmoothness.TransitionToExpanded",
), perfutil.StoreAllWithHeuristics(""))
// TODO(crbug.com/1141164): add notification-related metrics once it's
// supported.
// Check the validity of histogram data.
for _, err := range r.Values().Verify(ctx, expects) {
s.Error("Performance expectation missed: ", err)
}
// Storing the results for the future analyses.
if err := r.Values().Save(ctx, s.OutDir()); err != nil {
s.Error("Failed to save the values: ", err)
}
}