blob: f4cee674352b1b2cac33b57022cfa8b200367ee9 [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/local/action"
"chromiumos/tast/local/bundles/cros/ui/perfutil"
"chromiumos/tast/local/chrome"
"chromiumos/tast/local/chrome/ash"
"chromiumos/tast/local/chrome/uiauto"
"chromiumos/tast/local/chrome/uiauto/event"
"chromiumos/tast/local/chrome/uiauto/faillog"
"chromiumos/tast/local/chrome/uiauto/nodewith"
"chromiumos/tast/local/chrome/uiauto/pointer"
"chromiumos/tast/local/coords"
"chromiumos/tast/local/input"
"chromiumos/tast/local/power"
"chromiumos/tast/local/ui"
"chromiumos/tast/testing"
"chromiumos/tast/testing/hwdep"
)
func init() {
testing.AddTest(&testing.Test{
Func: LauncherPageSwitchPerf,
Desc: "Measures smoothness of switching pages within the launcher",
Contacts: []string{
"newcomer@chromium.org", "tbarzic@chromium.org", "cros-launcher-prod-notifications@google.com",
"mukai@chromium.org", // original test author
"cros-system-ui-eng@google.com",
},
Attr: []string{"group:crosbolt", "crosbolt_perbuild"},
SoftwareDeps: []string{"chrome"},
HardwareDeps: hwdep.D(hwdep.InternalDisplay()),
Fixture: "chromeLoggedInWith100FakeApps",
Params: []testing.Param{
{
Name: "clamshell_mode",
Val: false,
},
{
Name: "tablet_mode",
Val: true,
},
},
Timeout: 3 * time.Minute,
})
}
func LauncherPageSwitchPerf(ctx context.Context, s *testing.State) {
// Ensure display on to record ui performance correctly.
if err := power.TurnOnDisplay(ctx); err != nil {
s.Fatal("Failed to turn on display: ", err)
}
const dragDuration = 2 * time.Second
cr := s.FixtValue().(*chrome.Chrome)
tconn, err := cr.TestAPIConn(ctx)
if err != nil {
s.Fatal("Failed to connect to test API: ", err)
}
defer faillog.DumpUITreeOnError(ctx, s.OutDir(), s.HasError, tconn)
inTabletMode := s.Param().(bool)
cleanup, err := ash.EnsureTabletModeEnabled(ctx, tconn, inTabletMode)
if err != nil {
s.Fatal("Failed to ensure in clamshell mode: ", err)
}
defer cleanup(ctx)
var pc pointer.Context
if inTabletMode {
var err error
if pc, err = pointer.NewTouch(ctx, tconn); err != nil {
s.Fatal("Failed to create a touch controller")
}
} else {
pc = pointer.NewMouse(tconn)
}
defer pc.Close()
if err := ash.CreateWindows(ctx, tconn, cr, ui.PerftestURL, 2); err != nil {
s.Fatal("Failed to create windows: ", err)
}
if !inTabletMode {
// In clamshell mode, turn all windows into normal state, so the desktop
// under the app-launcher has the combination of window and wallpaper. This
// is not the case of the tablet mode (since windows are always maximized in
// the tablet mode).
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 to normal: ", err)
}
}
// Currently tast test may show a couple of notifications like "sign-in error"
// and they may overlap with UI components of launcher. This prevents intended
// actions on certain devices and causes test failures. Open and close the
// quick settings to dismiss those notification popups. See
// https://crbug.com/1084185.
if err := ash.CloseNotifications(ctx, tconn); err != nil {
s.Fatal("Failed to open/close the quick settings: ", err)
}
// Search or Shift-Search key to show the apps grid in fullscreen.
kw, err := input.Keyboard(ctx)
if err != nil {
s.Fatal("Failed to obtain the keyboard")
}
defer kw.Close()
accel := "Shift+Search"
if inTabletMode {
accel = "Search"
}
if err := kw.Accel(ctx, accel); err != nil {
s.Fatalf("Failed to type %s: %v", accel, err)
}
if !inTabletMode {
// Press the search key to close the app-list at the end. This is not
// necessary on tablet mode.
defer kw.Accel(ctx, "Search")
}
// Wait for the launcher state change.
if err := ash.WaitForLauncherState(ctx, tconn, ash.FullscreenAllApps); err != nil {
s.Fatal("Failed to wait: ", err)
}
// Find the apps grid view bounds.
ac := uiauto.New(tconn)
appsGridView := nodewith.ClassName("AppsGridView")
pageSwitcher := nodewith.ClassName("PageSwitcher")
pageButtons := nodewith.ClassName("Button").Ancestor(pageSwitcher)
buttonsInfo, err := ac.NodesInfo(ctx, pageButtons)
if err != nil {
s.Fatal("Failed to find the page switcher buttons: ", err)
}
if len(buttonsInfo) < 3 {
s.Fatalf("There are too few pages (%d), want more than 2 pages", len(buttonsInfo))
}
suffix := "ClamshellMode"
if inTabletMode {
suffix = "TabletMode"
}
runner := perfutil.NewRunner(cr)
const pageSwitchTimeout = 2 * time.Second
clickPageButtonAndWait := func(idx int) action.Action {
return ac.WaitForEvent(pageSwitcher, event.Alert, pc.Click(pageButtons.Nth(idx)))
}
// First: scroll by click. Clicking the second one, clicking the first one to
// go back, clicking the last one to long-jump, clicking the first one again
// to long-jump back to the original page.
s.Log("Starting the scroll by click")
runner.RunMultiple(ctx, s, "click", perfutil.RunAndWaitAll(tconn, action.Combine(
"switch page by buttons",
clickPageButtonAndWait(1),
clickPageButtonAndWait(0),
clickPageButtonAndWait(len(buttonsInfo)-1),
clickPageButtonAndWait(0),
), "Apps.PaginationTransition.AnimationSmoothness."+suffix),
perfutil.StoreSmoothness)
// Second: scroll by drags. This involves two types of operations, drag-up
// from the bottom for scrolling to the next page, and the drag-down from the
// top for scrolling to the previous page. In order to prevent overscrolling
// on the first page which might cause unexpected effects (like closing of the
// launcher itself), and because the first page might be in a special
// arrangement of the default apps with blank area at the bottom which
// prevents the drag-up gesture, switches to the second page before starting
// this scenario.
s.Log("Starting the scroll by drags")
if err := clickPageButtonAndWait(1)(ctx); err != nil {
s.Fatal("Failed to switch to the second page: ", err)
}
// Reset back to the first page at the end of the test; apps-grid page
// selection may stay in the last page, and that causes troubles on another
// test. See https://crbug.com/1081285.
defer func() {
if err := pc.Click(pageButtons.First())(ctx); err != nil {
s.Fatal("Failed to click the first page button: ", err)
}
}()
appsGridLocation, err := ac.Location(ctx, appsGridView)
if err != nil {
s.Fatal("Failed to get the location of appsgridview: ", err)
}
// drag-up gesture positions; starting at a bottom part of the apps-grid (at
// 4/5 height), and moves the height of the apps-grid. The starting height
// shouldn't be very bottom of the apps-grid, since it may fall into the
// hotseat (the gesture won't cause page-switch in such case).
// The X position should not be the center of the width since it would fall
// into an app icon. For now, it just sets 2/5 width position to avoid app
// icons.
dragUpStart := coords.NewPoint(
appsGridLocation.Left+appsGridLocation.Width*2/5,
appsGridLocation.Top+appsGridLocation.Height*4/5)
dragUpEnd := coords.NewPoint(dragUpStart.X, dragUpStart.Y-appsGridLocation.Height)
// drag-down gesture positions; starting at the top of the apps-grid (but
// not at the edge), and moves to the bottom edge of the apps-grid. Same
// X position as the drag-up gesture.
dragDownStart := coords.NewPoint(dragUpStart.X, appsGridLocation.Top+1)
dragDownEnd := coords.NewPoint(dragDownStart.X, dragDownStart.Y+appsGridLocation.Height)
runner.RunMultiple(ctx, s, "drag", perfutil.RunAndWaitAll(tconn, action.Combine(
"launcher page drag",
// Drag-up operation.
ac.WaitForEvent(pageSwitcher, event.Alert, pc.Drag(dragUpStart, pc.DragTo(dragUpEnd, dragDuration))),
// Drag-down operation.
ac.WaitForEvent(pageSwitcher, event.Alert, pc.Drag(dragDownStart, pc.DragTo(dragDownEnd, dragDuration))),
),
"Apps.PaginationTransition.DragScroll.PresentationTime."+suffix,
"Apps.PaginationTransition.DragScroll.PresentationTime.MaxLatency."+suffix),
perfutil.StoreLatency)
if err := runner.Values().Save(ctx, s.OutDir()); err != nil {
s.Fatal("Failed saving perf data: ", err)
}
}