blob: 53e1a08637db5fe031b8eb48688b214ee5276d17 [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 launcher
import (
"context"
"fmt"
"time"
"chromiumos/tast/ctxutil"
"chromiumos/tast/errors"
"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/launcher"
"chromiumos/tast/local/chrome/uiauto/nodewith"
"chromiumos/tast/local/chrome/uiauto/state"
"chromiumos/tast/local/input"
"chromiumos/tast/testing"
"chromiumos/tast/testing/hwdep"
)
func init() {
testing.AddTest(&testing.Test{
Func: FolderDragAndDrop,
LacrosStatus: testing.LacrosVariantUnknown,
Desc: "Launcher Folder Item Drag and Drop",
Contacts: []string{
"chromeos-sw-engprod@google.com",
"tbarzic@chromium.org",
"cros-system-ui-eng@google.com",
},
Attr: []string{"group:mainline", "informational"},
SoftwareDeps: []string{"chrome"},
Params: []testing.Param{{
Name: "productivity_launcher_clamshell_mode",
Val: launcher.TestCase{ProductivityLauncher: true, TabletMode: false},
Fixture: "chromeLoggedInWith100FakeAppsProductivityLauncher",
}, {
Name: "clamshell_mode",
Val: launcher.TestCase{ProductivityLauncher: false, TabletMode: false},
Fixture: "chromeLoggedInWith100FakeApps",
}, {
Name: "productivity_launcher_tablet_mode",
Val: launcher.TestCase{ProductivityLauncher: true, TabletMode: true},
ExtraHardwareDeps: hwdep.D(hwdep.InternalDisplay()),
Fixture: "chromeLoggedInWith100FakeAppsProductivityLauncher",
}, {
Name: "tablet_mode",
Val: launcher.TestCase{ProductivityLauncher: false, TabletMode: true},
ExtraHardwareDeps: hwdep.D(hwdep.InternalDisplay()),
Fixture: "chromeLoggedInWith100FakeApps",
}},
})
}
// FolderDragAndDrop runs a test that drags app list folders within the launcher UI.
func FolderDragAndDrop(ctx context.Context, s *testing.State) {
cr := s.FixtValue().(*chrome.Chrome)
cleanupCtx := ctx
ctx, cancel := ctxutil.Shorten(ctx, 10*time.Second)
defer cancel()
tconn, err := cr.TestAPIConn(ctx)
if err != nil {
s.Fatal("Failed to connect Test API: ", err)
}
defer faillog.DumpUITreeOnError(cleanupCtx, s.OutDir(), s.HasError, tconn)
kb, err := input.Keyboard(ctx)
if err != nil {
s.Fatal("Failed to get keyboard: ", err)
}
defer kb.Close()
testCase := s.Param().(launcher.TestCase)
tabletMode := testCase.TabletMode
productivityLauncher := testCase.ProductivityLauncher
cleanup, err := ash.EnsureTabletModeEnabled(ctx, tconn, tabletMode)
if err != nil {
s.Fatal("Failed to ensure clamshell/tablet mode: ", err)
}
defer cleanup(cleanupCtx)
if !tabletMode {
if err := ash.WaitForLauncherState(ctx, tconn, ash.Closed); err != nil {
s.Fatal("Launcher not closed after transition to clamshell mode: ", err)
}
}
usingBubbleLauncher := productivityLauncher && !tabletMode
// Open the Launcher and go to Apps list page.
if usingBubbleLauncher {
if err := launcher.OpenBubbleLauncher(tconn)(ctx); err != nil {
s.Fatal("Failed to open bubble launcher: ", err)
}
} else {
if err := launcher.OpenExpandedView(tconn)(ctx); err != nil {
s.Fatal("Failed to open Expanded Application list view: ", err)
}
}
if err := launcher.WaitForStableNumberOfApps(ctx, tconn); err != nil {
s.Fatal("Failed to wait for item count in app list to stabilize: ", err)
}
ui := uiauto.New(tconn)
if !usingBubbleLauncher {
pageSwitcher := nodewith.ClassName("Button").Ancestor(nodewith.ClassName("PageSwitcher"))
if err := ui.WithTimeout(5 * time.Second).LeftClick(pageSwitcher.Nth(0))(ctx); err != nil {
s.Fatal("Failed to switch launcher to first page: ", err)
}
}
// Create a folder that will be dragged around in the test.
if err := launcher.CreateFolder(ctx, tconn, productivityLauncher); err != nil {
s.Fatal("Failed to create a folder item: ", err)
}
folderName := fmt.Sprintf("FolderDnD %t %t", productivityLauncher, tabletMode)
if err := launcher.RenameFolder(tconn, kb, launcher.UnnamedFolderFinder.First(), folderName)(ctx); err != nil {
s.Fatal("Failed to rename test folder")
}
folderItemName := "Folder " + folderName
folderFinder := nodewith.ClassName(launcher.ExpandedItemsClass).Name(folderItemName)
if err := ui.Exists(folderFinder)(ctx); err != nil {
s.Fatal("Unable to find test folder: ", err)
}
// For paged launcher, start by dragging the folder to the second page - when productivity launcher is disabled,
// the first page contains only default apps, and may not have enough items to test drag within the current page.
if !usingBubbleLauncher {
if err := launcher.DragIconToNextPage(tconn, folderFinder)(ctx); err != nil {
s.Fatal("Failed to drag folder to the next page: ", err)
}
// Verify that the first item in the grid is offscreen.
firstItemInfo, err := ui.Info(ctx, nodewith.ClassName(launcher.ExpandedItemsClass).First())
if err != nil {
s.Fatal("Failed to get first item info after drag to second page")
}
if !firstItemInfo.State[state.Offscreen] {
s.Fatal("First item unexpectedly on sreen after drag to second page")
}
}
firstVisibleIndex, err := launcher.FirstNonRecentAppItem(ctx, tconn)
if err != nil {
s.Fatal("Failed to count recent apps items: ", err)
}
firstVisibleIndex, err = launcher.IndexOfFirstVisibleItem(ctx, tconn, firstVisibleIndex)
if err != nil {
s.Fatal("Failed to find visible item: ", err)
}
firstVisibleItemInfo, err := ui.Info(ctx, nodewith.ClassName(launcher.ExpandedItemsClass).Nth(firstVisibleIndex))
if err != nil {
s.Fatal("Failed to get first visible item info: ", err)
}
// Verify that the folder item is the first visible non-recent app item on the page.
if firstVisibleItemInfo.Name != folderItemName {
s.Fatalf("Folder is not the first visible item: %s", firstVisibleItemInfo.Name)
}
// Test folder drag within the current page.
targetIndex := firstVisibleIndex + 6
targetItem := nodewith.ClassName(launcher.ExpandedItemsClass).Nth(targetIndex)
if err := launcher.DragItemAfterItem(tconn, folderFinder, targetItem)(ctx); err != nil {
s.Fatal("Failed to drag folder within the same page: ", err)
}
// Verify the new folder position.
targetItemInfo, err := ui.Info(ctx, targetItem)
if err != nil {
s.Fatal("Failed to get target item info: ", err)
}
if targetItemInfo.Name != folderItemName {
s.Fatalf("Item at target name (%s) not the folder item (%s)", targetItemInfo.Name, folderItemName)
}
// Verify the folder is still onscreen.
folderInfo, err := ui.Info(ctx, folderFinder)
if err != nil {
s.Fatal("Failed to get updated folder info within the current page: ", err)
}
if folderInfo.State[state.Offscreen] {
s.Fatal("First item unexpectedly off sreen after drag within current page")
}
// If launcher is paginated, test dragging the folder to the previous page.
if !usingBubbleLauncher {
if err := launcher.DragIconToNeighbourPage(tconn, folderFinder, false /*next*/)(ctx); err != nil {
s.Fatal("Failed to drag folder to previous page: ", err)
}
// Verify that the first item is on the current page.
firstItemVisible, err := launcher.IsItemOnCurrentPage(ctx, tconn, nodewith.ClassName(launcher.ExpandedItemsClass).First())
if err != nil {
s.Fatal("Failed to query first item visibility after drag to first page: ", err)
}
if !firstItemVisible {
s.Fatal("First item unexpectedly off sreen after drag to first page")
}
// Verify the folder is still onscreen
folderVisible, err := launcher.IsItemOnCurrentPage(ctx, tconn, folderFinder)
if err != nil {
s.Fatal("Failed to query folder visibility after drag to first page: ", err)
}
if !folderVisible {
s.Fatal("First item unexpectedly off sreen after drag to first page")
}
}
// For bubble launcher, that apps grid can be scrolled by dragging the folder.
if usingBubbleLauncher {
if err := dragFolderAndScrollContainer(ctx, tconn, ui, folderFinder, false /*up*/); err != nil {
s.Fatal("Failed to drag the first icon to bottom of scrollable container: ", err)
}
if err := dragFolderAndScrollContainer(ctx, tconn, ui, folderFinder, true /*up*/); err != nil {
s.Fatal("Failed to drag the last item to top of scrollable container: ", err)
}
// Workaround for https://crbug.com/1290569 - item locations within a scroll view (which is used
// by bubble launcher) may be incorrect if the container node location gets updated when scroll
// offset is non-zero. This happens when opening and closing a folder, which is an action that
// the test is about to perform - reset the bubble scroll position so app list item node locations
// can be trusted.
scrollView := nodewith.ClassName("ScrollView").Ancestor(nodewith.ClassName("AppListBubbleView"))
if err := ui.ResetScrollOffset(scrollView)(ctx); err != nil {
s.Fatal("Failed to reset scroll offset: ", err)
}
}
// Clean up the folder used during the test.
if err := launcher.RemoveIconFromFolder(tconn, folderFinder)(ctx); err != nil {
s.Fatal("Failed to drag out the icon from folder: ", err)
}
if productivityLauncher {
if err := launcher.RemoveIconFromFolder(tconn, folderFinder)(ctx); err != nil {
s.Fatal("Failed to drag out the icon from single-item folder: ", err)
}
}
// Make sure that the folder has closed.
if err := ui.WaitUntilGone(folderFinder)(ctx); err != nil {
errors.Wrap(err, "folder item is not gone")
}
}
// dragFolderAndScrollContainer drags a folder item in the scrollable apps grid view and scrolls is as need.
// If up is true, the container will be scrolled upwards, and the item will be dropped in the second slot in the apps grid.
// If up is false, the container will be scrolled downwards, and the item will be dropped in the last slot in the apps grid.
// Verifies the item at drop location matches the dragged item, and that it's onscreen.
func dragFolderAndScrollContainer(ctx context.Context, tconn *chrome.TestConn, ui *uiauto.Context, dragItem *nodewith.Finder, up bool) error {
dragItemInfo, err := ui.Info(ctx, dragItem)
if err != nil {
return errors.Wrap(err, "failed to get drag item info")
}
dragItemName := dragItemInfo.Name
scrollableGridItems := nodewith.ClassName(launcher.ExpandedItemsClass).Ancestor(nodewith.ClassName("ScrollableAppsGridView"))
allItemsInfo, err := ui.NodesInfo(ctx, scrollableGridItems)
if err != nil {
return errors.Wrap(err, "failed to get list of grid items")
}
itemCount := len(allItemsInfo)
// Index of the item to be used as a refecence for dropping the dragged view - the view will be dropped right of the item.
var targetIndex int
// The expected index in the apps grid in which the dragged item will be dropped.
var dropIndex int
if up {
targetIndex = 0
dropIndex = 1
} else {
targetIndex = itemCount - 1
dropIndex = itemCount - 1
}
targetItem := scrollableGridItems.Nth(targetIndex)
if err := launcher.DragItemInBubbleLauncherWithScrolling(ctx, tconn, ui, dragItem, targetItem, up); err != nil {
return errors.Wrap(err, "bubble launcher scroll failed")
}
// Drag item should have been moved right of the first item in the scrollable grid.
dropItemFinder := scrollableGridItems.Nth(dropIndex)
dropItemInfo, err := ui.Info(ctx, dropItemFinder)
if err != nil {
return errors.Wrap(err, "failed to get second app item info")
}
if dragItemName != dropItemInfo.Name {
return errors.Wrapf(err, "Last item %s is not the drag item %s", dropItemInfo.Name, dragItemName)
}
if dropItemInfo.State[state.Offscreen] {
return errors.New("drag item offscreen after drag with scroll")
}
return nil
}