blob: 98a0f406f38e01b3ec7cc7a2c7e952357b8aeabf [file]
// 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 filemanager
import (
"context"
"os"
"path/filepath"
"time"
"chromiumos/tast/errors"
"chromiumos/tast/fsutil"
"chromiumos/tast/local/chrome"
"chromiumos/tast/local/chrome/uiauto"
"chromiumos/tast/local/chrome/uiauto/faillog"
"chromiumos/tast/local/chrome/uiauto/filesapp"
"chromiumos/tast/local/chrome/uiauto/nodewith"
"chromiumos/tast/local/chrome/uiauto/role"
"chromiumos/tast/local/input"
"chromiumos/tast/testing"
)
// TestFunc contains the contents of the test itself.
type testFunc func(ctx context.Context, s *testing.State, files *filesapp.FilesApp, zipFiles []string)
// TestEntry contains the function used in the test along with the names of the ZIP files the test is using.
type testEntry struct {
TestCase testFunc
ZipFiles []string
}
func init() {
testing.AddTest(&testing.Test{
Func: ZipMount,
LacrosStatus: testing.LacrosVariantUnneeded,
Desc: "Tests Files app mounting workflow",
Contacts: []string{
"jboulic@chromium.org",
"fdegros@chromium.org",
"chromeos-files-syd@google.com",
},
Attr: []string{"group:mainline", "informational"},
SoftwareDeps: []string{"chrome"},
Data: []string{
"Encrypted_AES-256.zip",
"Encrypted_ZipCrypto.zip",
"Texts.7z",
"Texts.rar",
"Texts.zip",
},
Params: []testing.Param{{
Name: "mount_single_7z",
Val: testEntry{
TestCase: testMountingSingleZipFile,
ZipFiles: []string{"Texts.7z"},
},
}, {
Name: "mount_single_rar",
Val: testEntry{
TestCase: testMountingSingleZipFile,
ZipFiles: []string{"Texts.rar"},
},
}, {
Name: "mount_single_zip",
Val: testEntry{
TestCase: testMountingSingleZipFile,
ZipFiles: []string{"Texts.zip"},
},
}, {
Name: "cancel_multiple",
Val: testEntry{
TestCase: testCancelingMultiplePasswordDialogs,
ZipFiles: []string{"Encrypted_AES-256.zip", "Encrypted_ZipCrypto.zip"},
},
}, {
Name: "mount_multiple",
Val: testEntry{
TestCase: testMountingMultipleZipFiles,
ZipFiles: []string{"Encrypted_AES-256.zip", "Encrypted_ZipCrypto.zip", "Texts.zip"},
},
}},
})
}
func ZipMount(ctx context.Context, s *testing.State) {
testParams := s.Param().(testEntry)
zipFiles := testParams.ZipFiles
// TODO(nigeltao): remove "FilesArchivemount" after it gets flipped to
// enabled-by-default (scheduled for M94) and before the feature flag
// expires (scheduled for M100). crbug.com/1216245
cr, err := chrome.New(ctx, chrome.EnableFeatures("FilesArchivemount"))
if err != nil {
s.Fatal("Cannot start Chrome: ", err)
}
defer cr.Close(ctx)
// Load ZIP files.
for _, zipFile := range zipFiles {
zipFileLocation := filepath.Join(filesapp.DownloadPath, zipFile)
if err := fsutil.CopyFile(s.DataPath(zipFile), zipFileLocation); err != nil {
s.Fatalf("Cannot copy ZIP file to %s: %s", zipFileLocation, err)
}
defer os.Remove(zipFileLocation)
}
// Open the test API.
tconn, err := cr.TestAPIConn(ctx)
if err != nil {
s.Fatal("Cannot create test API connection: ", err)
}
defer faillog.DumpUITreeOnError(ctx, s.OutDir(), s.HasError, tconn)
// Open the Files App.
files, err := filesapp.Launch(ctx, tconn)
if err != nil {
s.Fatal("Cannot launch the Files App: ", err)
}
// Open the Downloads folder.
if err := files.OpenDownloads()(ctx); err != nil {
s.Fatal("Cannot open Downloads folder: ", err)
}
// Wait for the ZIP files to load in the file list.
for _, zipFile := range zipFiles {
if err := files.WaitForFile(zipFile)(ctx); err != nil {
s.Fatalf("Cannot find %s: %s", zipFile, err)
}
}
testParams.TestCase(ctx, s, files, zipFiles)
}
// waitUntilPasswordDialogExists waits until the password dialog is displayed and returns the expected ZIP file name to which it is associated.
func waitUntilPasswordDialogExists(ctx context.Context, files *filesapp.FilesApp, processedZipFiles map[string]bool) (string, error) {
// Zip file name to be returned, to which the password dialog is associated.
zipFileName := ""
// Wait until the password dialog is displayed for one of the expected ZIP files.
if err := testing.Poll(ctx, func(ctx context.Context) error {
passwordDialog := nodewith.Role(role.Dialog)
// Wait for a dialog to open.
if err := files.WithTimeout(5 * time.Second).WaitUntilExists(passwordDialog)(ctx); err != nil {
return errors.Wrap(err, "cannot find dialog")
}
// Look for one of the expected ZIP file names within the dialog.
nodes, err := files.NodesInfo(ctx, nodewith.Role(role.StaticText).Ancestor(passwordDialog))
if err != nil || len(nodes) == 0 {
return errors.New("cannot find static text in dialog")
}
// Match the text found in the dialog with the zipFiles for which we still expect a password dialog.
for _, n := range nodes {
if _, exists := processedZipFiles[n.Name]; exists {
zipFileName = n.Name
return nil
}
}
return errors.New("cannot find expected ZIP file name in dialog")
}, &testing.PollOptions{Timeout: 15 * time.Second}); err != nil {
return "", err
}
return zipFileName, nil
}
// checkAndUnmountZipFile checks that a given ZIP file is correctly mounted and click the 'eject' button to unmount it.
func checkAndUnmountZipFile(ctx context.Context, s *testing.State, files *filesapp.FilesApp, zipFile string) {
// Find and open the mounted ZIP file.
zipFileNode := nodewith.Name(zipFile).Role(role.TreeItem)
if err := uiauto.Combine("find and click tree item",
files.WithTimeout(5*time.Second).WaitUntilExists(zipFileNode),
files.LeftClick(zipFileNode),
)(ctx); err != nil {
s.Fatal("Cannot find and open the mounted ZIP file: ", err)
}
// Ensure that the Files App is displaying the content of the mounted ZIP file.
rootWebArea := nodewith.Name("Files - " + zipFile).Role(role.RootWebArea)
if err := files.WithTimeout(5 * time.Second).WaitUntilExists(rootWebArea)(ctx); err != nil {
s.Fatal("Cannot see content of mounted ZIP file: ", err)
}
// The test ZIP files are all expected to contain a single "Texts" folder.
var zipContentDirectoryLabel = "Texts"
// Check content of mounted ZIP file.
label := nodewith.Name(zipContentDirectoryLabel).Role(role.ListBoxOption)
if err := files.WithTimeout(5 * time.Second).WaitUntilExists(label)(ctx); err != nil {
s.Fatalf("Cannot see directory %s in mounted ZIP file: %v", zipContentDirectoryLabel, err)
}
// Find the eject button within the appropriate tree item.
ejectButton := nodewith.Name("Eject device").Role(role.Button).Ancestor(zipFileNode)
if err := uiauto.Combine("find and click eject button - "+zipFile,
files.WithTimeout(5*time.Second).WaitUntilExists(ejectButton),
files.LeftClick(ejectButton),
)(ctx); err != nil {
s.Fatal("Cannot find the eject button within the appropriate tree item: ", err)
}
// Check that the tree item corresponding to the previously mounted ZIP file was removed.
if err := files.WithTimeout(5 * time.Second).WaitUntilGone(zipFileNode)(ctx); err != nil {
s.Fatalf("%s is still mounted: %v", zipFile, err)
}
}
func testMountingSingleZipFile(ctx context.Context, s *testing.State, files *filesapp.FilesApp, zipFiles []string) {
if len(zipFiles) != 1 {
s.Fatal("Unexpected length for zipFiles")
}
zipFile := zipFiles[0]
// Select ZIP file.
if err := uiauto.Combine("wait for test ZIP file and select",
files.WithTimeout(5*time.Second).WaitForFile(zipFile),
files.SelectFile(zipFile),
)(ctx); err != nil {
s.Fatal("Cannot select ZIP file: ", err)
}
// Wait for Open button in the top bar.
open := nodewith.Name("Open").Role(role.Button)
if err := uiauto.Combine("find and click Open menu item",
files.WithTimeout(5*time.Second).WaitUntilExists(open),
files.LeftClick(open),
)(ctx); err != nil {
s.Fatal("Cannot unmount Zip file: ", err)
}
checkAndUnmountZipFile(ctx, s, files, zipFile)
}
func testCancelingMultiplePasswordDialogs(ctx context.Context, s *testing.State, files *filesapp.FilesApp, zipFiles []string) {
// Define keyboard to perform keyboard shortcuts.
ew, err := input.Keyboard(ctx)
if err != nil {
s.Fatal("Cannot create keyboard: ", err)
}
defer ew.Close()
// Select the 2 encrypted ZIP files.
if err := files.SelectMultipleFiles(ew, zipFiles...)(ctx); err != nil {
s.Fatal("Cannot perform multi-selection on the encrypted files: ", err)
}
// Press Enter.
if err := ew.Accel(ctx, "Enter"); err != nil {
s.Fatal("Cannot press 'Enter': ", err)
}
// Define a map of zipFileNames for which we still expect a password dialog.
processedZipFiles := make(map[string]bool)
for _, zipFile := range zipFiles {
processedZipFiles[zipFile] = true
}
// Wait until the password dialog is active for one of the encrypted ZIP archives.
firstZipFileName, err1 := waitUntilPasswordDialogExists(ctx, files, processedZipFiles)
if err1 != nil {
s.Fatal("Cannot find password dialog: ", err1)
}
// We don't expect another password dialog for this particular file.
delete(processedZipFiles, firstZipFileName)
// Press Esc.
if err := ew.Accel(ctx, "Esc"); err != nil {
s.Fatal("Cannot press 'Esc': ", err)
}
// Wait until the password dialog is active for the second encrypted ZIP archive.
if _, err2 := waitUntilPasswordDialogExists(ctx, files, processedZipFiles); err2 != nil {
s.Fatal("Cannot find password dialog: ", err2)
}
// Click the 'Cancel' button.
cancel := nodewith.Name("Cancel").Role(role.Button)
if err := uiauto.Combine("find and click password dialog cancel button",
files.WithTimeout(5*time.Second).WaitUntilExists(cancel),
files.LeftClick(cancel),
)(ctx); err != nil {
s.Fatal("Cannot click the 'Cancel' button: ", err)
}
// Checks that the password dialog is not displayed anymore.
passwordDialog := nodewith.Name("Password").Role(role.Dialog)
if err = files.WithTimeout(5 * time.Second).WaitUntilGone(passwordDialog)(ctx); err != nil {
s.Fatal("The password dialog is still displayed: ", err)
}
}
func testMountingMultipleZipFiles(ctx context.Context, s *testing.State, files *filesapp.FilesApp, zipFiles []string) {
// Define keyboard to perform keyboard shortcuts.
ew, err := input.Keyboard(ctx)
if err != nil {
s.Fatal("Cannot create keyboard: ", err)
}
defer ew.Close()
// Define a map of zipFileNames for which we still expect a password dialog.
processedZipFiles := make(map[string]bool)
for _, zipFile := range zipFiles {
processedZipFiles[zipFile] = true
}
// Select the 3 ZIP files. 2 of them are encrypted.
if err := files.SelectMultipleFiles(ew, zipFiles...)(ctx); err != nil {
s.Fatal("Cannot perform multi-selection on the encrypted files: ", err)
}
// Press Enter.
if err := ew.Accel(ctx, "Enter"); err != nil {
s.Fatal("Cannot press 'Enter': ", err)
}
// Wait until the password dialog is active for one of the encrypted ZIP archives.
firstZipFileName, err1 := waitUntilPasswordDialogExists(ctx, files, processedZipFiles)
if err1 != nil {
s.Fatal("Cannot find password dialog: ", err1)
}
// Enter wrong password.
node := nodewith.Role(role.TextField).Editable().Focusable().Protected().Focused()
if err := files.WithTimeout(15 * time.Second).WaitUntilExists(node)(ctx); err != nil {
s.Fatal("Cannot find password input field: ", err)
}
if err := ew.Type(ctx, "wrongPassword"); err != nil {
s.Fatal("Cannot enter 'wrongPassword': ", err)
}
// Validate password.
if err := ew.Accel(ctx, "Enter"); err != nil {
s.Fatal("Cannot validate password by pressing 'Enter': ", err)
}
// Check that the password dialog is still active.
firstZipFileName2, err := waitUntilPasswordDialogExists(ctx, files, processedZipFiles)
if err != nil {
s.Fatal("Cannot find password dialog: ", err)
}
if firstZipFileName != firstZipFileName2 {
s.Fatalf("Password dialog unexpectedly changed: %s to %s", firstZipFileName, firstZipFileName2)
}
// Check that "Invalid password" is displayed on the UI.
invalidPassword := nodewith.Name("Invalid password").Role(role.StaticText)
if err := files.WithTimeout(15 * time.Second).WaitUntilExists(invalidPassword)(ctx); err != nil {
s.Fatal("Cannot find 'Invalid password': ", err)
}
// Enter right password.
if err := ew.Type(ctx, "password"); err != nil {
s.Fatal("Cannot enter 'password': ", err)
}
// Validate password.
if err := ew.Accel(ctx, "Enter"); err != nil {
s.Fatal("Cannot validate the name of the new directory: ", err)
}
// We don't expect another password dialog for this particular file.
delete(processedZipFiles, firstZipFileName)
// Check that the password dialog is active for the second encrypted ZIP archive.
if _, err := waitUntilPasswordDialogExists(ctx, files, processedZipFiles); err != nil {
s.Fatal("Cannot find password dialog: ", err)
}
// Enter right password.
if err := ew.Type(ctx, "password"); err != nil {
s.Fatal("Cannot enter 'password': ", err)
}
// Click Unlock.
unlock := nodewith.Name("Unlock").Role(role.Button)
if err := uiauto.Combine("find password dialog cancel button and validate password",
files.WithTimeout(5*time.Second).WaitUntilExists(unlock),
files.LeftClick(unlock),
)(ctx); err != nil {
s.Fatal("Cannot find password dialog cancel button: ", err)
}
// Checks that the password dialog is not displayed anymore.
password := nodewith.Name("Password").Role(role.Dialog)
if err = files.WithTimeout(5 * time.Second).WaitUntilGone(password)(ctx); err != nil {
s.Fatal("The password dialog is still displayed: ", err)
}
// Check that the 3 zip files have been mounted correctly and unmount them.
for _, zipFile := range zipFiles {
checkAndUnmountZipFile(ctx, s, files, zipFile)
}
}