blob: b2ae2aec46b0bff2eb9af31fb5166a3b4713fae4 [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 arc
import (
"context"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"time"
"chromiumos/tast/common/android/ui"
"chromiumos/tast/ctxutil"
"chromiumos/tast/errors"
"chromiumos/tast/fsutil"
"chromiumos/tast/local/arc"
"chromiumos/tast/local/chrome"
"chromiumos/tast/local/chrome/ash"
"chromiumos/tast/local/chrome/uiauto/mouse"
"chromiumos/tast/local/coords"
"chromiumos/tast/testing"
)
type dragDropTestArgs struct {
extensionPrefix string
androidSource bool
androidTarget bool
}
func init() {
testing.AddTest(&testing.Test{
Func: DragDrop,
LacrosStatus: testing.LacrosVariantNeeded,
Desc: "Checks drag and drop support from Chrome to ARC",
Contacts: []string{"yhanada@chromium.org", "arc-framework+tast@google.com", "cros-arc-te@google.com"},
Attr: []string{"group:mainline", "group:arc-functional"},
SoftwareDeps: []string{"chrome"},
Data: []string{
"drag_drop_manifest.json", "drag_source_background.js", "drag_source_window.js", "drag_source_window.html",
"drag_target_background.js", "drag_target_window.js", "drag_target_window.html"},
Timeout: 4 * time.Minute,
Params: []testing.Param{{
Name: "chrome_to_android",
ExtraSoftwareDeps: []string{"android_p"},
Val: &dragDropTestArgs{
extensionPrefix: "drag_source_",
androidSource: false,
androidTarget: true,
},
}, {
Name: "chrome_to_android_vm",
ExtraAttr: []string{"informational"},
ExtraSoftwareDeps: []string{"android_vm"},
Val: &dragDropTestArgs{
extensionPrefix: "drag_source_",
androidSource: false,
androidTarget: true,
},
}, {
Name: "android_to_android",
ExtraAttr: []string{"informational"},
ExtraSoftwareDeps: []string{"android_p"},
Val: &dragDropTestArgs{
androidSource: true,
androidTarget: true,
},
}, {
Name: "android_to_android_vm",
ExtraAttr: []string{"informational"},
ExtraSoftwareDeps: []string{"android_vm"},
Val: &dragDropTestArgs{
androidSource: true,
androidTarget: true,
},
}, {
Name: "android_to_chrome",
ExtraAttr: []string{"informational"},
ExtraSoftwareDeps: []string{"android_p"},
Val: &dragDropTestArgs{
extensionPrefix: "drag_target_",
androidSource: true,
androidTarget: false,
},
}, {
Name: "android_to_chrome_vm",
ExtraAttr: []string{"informational"},
ExtraSoftwareDeps: []string{"android_vm"},
Val: &dragDropTestArgs{
extensionPrefix: "drag_target_",
androidSource: true,
androidTarget: false,
},
}},
})
}
func DragDrop(ctx context.Context, s *testing.State) {
const (
sourceApk = "ArcDragSourceTest.apk"
sourcePkg = "org.chromium.arc.testapp.dragsource"
sourceActName = ".DragSourceActivity"
targetApk = "ArcDragTargetTest.apk"
targetPkg = "org.chromium.arc.testapp.dragtarget"
targetActName = ".DragTargetActivity"
// width and height of target and source windows.
w = 500
)
cleanupCtx := ctx
ctx, cancel := ctxutil.Shorten(ctx, time.Second*10)
defer cancel()
args := s.Param().(*dragDropTestArgs)
var extID string
chromeOpts := []chrome.Option{chrome.ARCEnabled(), chrome.ExtraArgs("--force-tablet-mode=clamshell"), chrome.ExtraArgs("--disable-features=ArcResizeLock")}
if args.extensionPrefix != "" {
s.Log("Copying extension to temp directory")
extDir, err := ioutil.TempDir("", "tast.arc.DragDropExtension")
if err != nil {
s.Fatal("Failed to create temp dir: ", err)
}
defer os.RemoveAll(extDir)
if err := fsutil.CopyFile(s.DataPath("drag_drop_manifest.json"), filepath.Join(extDir, "manifest.json")); err != nil {
s.Fatal("Failed to copy extension manifest.json: ", err)
}
for _, name := range []string{"background.js", "window.js", "window.html"} {
if err := fsutil.CopyFile(s.DataPath(args.extensionPrefix+name), filepath.Join(extDir, name)); err != nil {
s.Fatalf("Failed to copy extension %s: %v", name, err)
}
}
extID, err = chrome.ComputeExtensionID(extDir)
if err != nil {
s.Fatalf("Failed to compute extension ID for %v: %v", extDir, err)
}
chromeOpts = append(chromeOpts, chrome.UnpackedExtension(extDir))
}
s.Log("Starting browser instance")
cr, err := chrome.New(ctx, chromeOpts...)
if err != nil {
s.Fatal("Failed to connect to Chrome: ", err)
}
defer cr.Close(cleanupCtx)
tconn, err := cr.TestAPIConn(ctx)
if err != nil {
s.Fatal("Failed to create Test API connection: ", err)
}
a, err := arc.New(ctx, s.OutDir())
if err != nil {
s.Fatal("Could not start ARC: ", err)
}
defer a.Close(cleanupCtx)
d, err := a.NewUIDevice(ctx)
if err != nil {
s.Fatal("Failed initializing UI Automator: ", err)
}
defer d.Close(cleanupCtx)
startActivityWithBounds := func(ctx context.Context, apk, pkg, activityName string, wantBounds coords.Rect) (act *arc.Activity, err error) {
cleanupCtx := ctx
ctx, cancel := ctxutil.Shorten(ctx, time.Second*10)
defer cancel()
if err = a.Install(ctx, arc.APKPath(apk)); err != nil {
err = errors.Wrap(err, "failed installing app")
return
}
if act, err = arc.NewActivity(a, pkg, activityName); err != nil {
err = errors.Wrap(err, "failed to create a new activity")
return
}
if err = act.StartWithDefaultOptions(ctx, tconn); err != nil {
act.Close()
act = nil
err = errors.Wrap(err, "failed to start the activity")
return
}
defer func() {
if err != nil {
act.Stop(cleanupCtx, tconn)
act.Close()
act = nil
}
}()
var window *ash.Window
if window, err = ash.FindWindow(ctx, tconn, func(window *ash.Window) bool {
return window.ARCPackageName == pkg
}); err != nil {
err = errors.Wrap(err, "failed to find the ARC window")
return
}
if err = act.SetWindowState(ctx, tconn, arc.WindowStateNormal); err != nil {
err = errors.Wrap(err, "failed to set the window state to normal")
return
}
if err = ash.WaitForCondition(ctx, tconn, func(cur *ash.Window) bool {
return cur.ID == window.ID && cur.State == ash.WindowStateNormal && !cur.IsAnimating
}, &testing.PollOptions{Timeout: 5 * time.Second}); err != nil {
err = errors.Wrap(err, "failed to wait for the window to finish animating")
return
}
var gotBounds coords.Rect
if gotBounds, _, err = ash.SetWindowBounds(ctx, tconn, window.ID, wantBounds, window.DisplayID); err != nil {
err = errors.Wrap(err, "failed to set window bounds")
return
} else if gotBounds != wantBounds {
err = errors.Errorf("failed to resize the activity: got %v; want %v", gotBounds, wantBounds)
return
}
return
}
sourceBounds := coords.Rect{Left: w, Top: 0, Width: w, Height: w}
targetBounds := coords.Rect{Left: 0, Top: 0, Width: w, Height: w}
if args.androidSource {
sourceAct, err := startActivityWithBounds(ctx, sourceApk, sourcePkg, sourceActName, sourceBounds)
if err != nil {
s.Fatal("Failed to start an activity with bounds: ", err)
}
defer sourceAct.Close()
defer sourceAct.Stop(cleanupCtx, tconn)
}
var targetAct *arc.Activity
if args.androidTarget {
targetAct, err = startActivityWithBounds(ctx, targetApk, targetPkg, targetActName, targetBounds)
if err != nil {
s.Fatal("Failed to start an activity with bounds: ", err)
}
defer targetAct.Close()
defer targetAct.Stop(cleanupCtx, tconn)
}
if err := mouse.Drag(tconn, sourceBounds.CenterPoint(), targetBounds.CenterPoint(), time.Second)(ctx); err != nil {
s.Fatal("Failed to send drag events: ", err)
}
if args.androidTarget {
if err := targetAct.Focus(ctx, tconn); err != nil {
s.Fatal("Failed to focus the activity: ", err)
}
const (
expected = `ClipData { text/plain "" {T:Data text} }`
fieldID = targetPkg + ":id/dropped_data_view"
)
if err := d.Object(ui.ID(fieldID)).WaitForText(ctx, expected, 30*time.Second); err != nil {
s.Fatal("Failed to wait for the drag and drop result: ", err)
}
} else {
s.Log("Connecting to the extension page")
bgURL := "chrome-extension://" + extID + "/window.html"
conn, err := cr.NewConnForTarget(ctx, chrome.MatchTargetURL(bgURL))
if err != nil {
s.Fatalf("Could not connect to extension at %v: %v", bgURL, err)
}
const expected = "Data text"
if err := conn.WaitForExpr(ctx, fmt.Sprintf(`document.getElementById('dropped-data').innerHTML === %q`, expected)); err != nil {
s.Fatal("Failed to wait for the dropped data: ", err)
}
}
}