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 (
func init() {
Func: LauncherPageSwitchPerf,
Desc: "Measures smoothness of switching pages within the launcher",
Contacts: []string{
"", "", "",
"", // original test author
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
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",
), "Apps.PaginationTransition.AnimationSmoothness."+suffix),
// 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
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(
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))),
if err := runner.Values().Save(ctx, s.OutDir()); err != nil {
s.Fatal("Failed saving perf data: ", err)