| // 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" |
| "os" |
| "path/filepath" |
| "time" |
| |
| "chromiumos/tast/common/action" |
| "chromiumos/tast/common/perf" |
| "chromiumos/tast/ctxutil" |
| "chromiumos/tast/local/audio/crastestclient" |
| "chromiumos/tast/local/bundles/cros/ui/cuj" |
| "chromiumos/tast/local/bundles/cros/ui/windowarrangementcuj" |
| "chromiumos/tast/local/chrome/ash" |
| "chromiumos/tast/local/chrome/browser" |
| "chromiumos/tast/local/chrome/uiauto" |
| "chromiumos/tast/local/chrome/uiauto/faillog" |
| "chromiumos/tast/local/chrome/uiauto/nodewith" |
| "chromiumos/tast/local/chrome/uiauto/pointer" |
| "chromiumos/tast/local/chrome/uiauto/role" |
| "chromiumos/tast/local/chrome/webutil" |
| "chromiumos/tast/local/cpu" |
| "chromiumos/tast/local/power" |
| "chromiumos/tast/testing" |
| "chromiumos/tast/testing/hwdep" |
| ) |
| |
| func init() { |
| testing.AddTest(&testing.Test{ |
| Func: WindowArrangementCUJ, |
| LacrosStatus: testing.LacrosVariantExists, |
| Desc: "Measures the performance of critical user journey for window arrangements", |
| Contacts: []string{"yichenz@chromium.org", "chromeos-perfmetrics-eng@google.com"}, |
| Attr: []string{"group:crosbolt", "crosbolt_perbuild", "group:cuj"}, |
| SoftwareDeps: []string{"chrome", "arc", "chrome_internal"}, |
| HardwareDeps: hwdep.D(hwdep.InternalDisplay()), |
| Vars: []string{"record"}, |
| Timeout: 10*time.Minute + cuj.CPUStablizationTimeout, |
| Data: []string{"shaka_720.webm", "pip.html"}, |
| Params: []testing.Param{ |
| { |
| Name: "clamshell_mode", |
| Val: windowarrangementcuj.TestParam{ |
| BrowserType: browser.TypeAsh, |
| }, |
| Fixture: "arcBootedInClamshellMode", |
| ExtraSoftwareDeps: []string{"android_p"}, |
| }, |
| { |
| Name: "tablet_mode", |
| Val: windowarrangementcuj.TestParam{ |
| BrowserType: browser.TypeAsh, |
| Tablet: true, |
| }, |
| ExtraSoftwareDeps: []string{"android_p"}, |
| }, |
| { |
| Name: "tablet_mode_trace", |
| Val: windowarrangementcuj.TestParam{ |
| BrowserType: browser.TypeAsh, |
| Tablet: true, |
| Tracing: true, |
| }, |
| ExtraSoftwareDeps: []string{"android_p"}, |
| }, |
| { |
| Name: "tablet_mode_validation", |
| Val: windowarrangementcuj.TestParam{ |
| BrowserType: browser.TypeAsh, |
| Tablet: true, |
| Validation: true, |
| }, |
| ExtraSoftwareDeps: []string{"android_p"}, |
| }, |
| { |
| Name: "lacros", |
| Val: windowarrangementcuj.TestParam{ |
| BrowserType: browser.TypeLacros, |
| }, |
| Fixture: "lacrosWithArcBooted", |
| ExtraSoftwareDeps: []string{"android_p", "lacros"}, |
| }, |
| { |
| Name: "vm", |
| Val: windowarrangementcuj.TestParam{ |
| BrowserType: browser.TypeAsh, |
| }, |
| Fixture: "arcBootedInClamshellMode", |
| ExtraSoftwareDeps: []string{"android_vm"}, |
| }, |
| }, |
| }) |
| } |
| |
| func WindowArrangementCUJ(ctx context.Context, s *testing.State) { |
| const ( |
| timeout = 10 * time.Second |
| duration = 2 * time.Second |
| ) |
| |
| // Ensure display on to record ui performance correctly. |
| if err := power.TurnOnDisplay(ctx); err != nil { |
| s.Fatal("Failed to turn on display: ", err) |
| } |
| |
| // Shorten context a bit to allow for cleanup. |
| closeCtx := ctx |
| ctx, cancel := ctxutil.Shorten(ctx, 2*time.Second) |
| defer cancel() |
| |
| testParam := s.Param().(windowarrangementcuj.TestParam) |
| tabletMode := testParam.Tablet |
| |
| conns, err := windowarrangementcuj.SetupChrome(ctx, closeCtx, s) |
| if err != nil { |
| s.Fatal("Failed to setup chrome: ", err) |
| } |
| defer conns.Cleanup(closeCtx) |
| |
| cleanup, err := ash.EnsureTabletModeEnabled(ctx, conns.TestConn, tabletMode) |
| if err != nil { |
| s.Fatal("Failed to ensure clamshell/tablet mode: ", err) |
| } |
| defer cleanup(closeCtx) |
| |
| // Wait for CPU to stabilize before test. |
| if err := cpu.WaitUntilStabilized(ctx, cuj.CPUCoolDownConfig()); err != nil { |
| // Log the cpu stabilizing wait failure instead of make it fatal. |
| // TODO(b/213238698): Include the error as part of test data. |
| s.Log("Failed to wait for CPU to become idle: ", err) |
| } |
| |
| tabChecker, err := cuj.NewTabCrashChecker(ctx, conns.TestConn) |
| if err != nil { |
| s.Fatal("Failed to create TabCrashChecker: ", err) |
| } |
| |
| if _, ok := s.Var("record"); ok { |
| screenRecorder, err := uiauto.NewScreenRecorder(ctx, conns.TestConn) |
| if err != nil { |
| s.Fatal("Failed to create ScreenRecorder: ", err) |
| } |
| defer func() { |
| screenRecorder.Stop(ctx) |
| dir, ok := testing.ContextOutDir(ctx) |
| if ok && dir != "" { |
| if _, err := os.Stat(dir); err == nil { |
| testing.ContextLogf(ctx, "Saving screen record to %s", dir) |
| if err := screenRecorder.SaveInBytes(ctx, filepath.Join(dir, "screen_record.webm")); err != nil { |
| s.Fatal("Failed to save screen record in bytes: ", err) |
| } |
| } |
| } |
| screenRecorder.Release(ctx) |
| }() |
| screenRecorder.Start(ctx, conns.TestConn) |
| } |
| |
| // Set up the cuj.Recorder: In clamshell mode, this test will measure the combinations of |
| // input latency of tab dragging and of window resizing and of split view resizing, and |
| // also the percent of dropped frames of video; In tablet mode, this test will measure |
| // the combinations of input latency of tab dragging and of input latency of split view |
| // resizing and the percent of dropped frames of video. |
| configs := []cuj.MetricConfig{ |
| // Ash metrics config, always collected from ash-chrome. |
| cuj.NewCustomMetricConfig( |
| "Ash.Smoothness.PercentDroppedFrames_1sWindow", "percent", |
| perf.SmallerIsBetter, []int64{50, 80}), |
| cuj.NewCustomMetricConfig( |
| "Browser.Responsiveness.JankyIntervalsPerThirtySeconds3", "janks", |
| perf.SmallerIsBetter, []int64{0, 3}), |
| } |
| if !tabletMode { |
| configs = []cuj.MetricConfig{ |
| cuj.NewLatencyMetricConfig("Ash.TabDrag.PresentationTime.ClamshellMode"), |
| cuj.NewLatencyMetricConfig("Ash.InteractiveWindowResize.TimeToPresent"), |
| cuj.NewLatencyMetricConfig("Ash.SplitViewResize.PresentationTime.ClamshellMode.SingleWindow"), |
| cuj.NewLatencyMetricConfig("Ash.SplitViewResize.PresentationTime.ClamshellMode.WithOverview"), |
| cuj.NewCustomMetricConfigWithTestConn( |
| "Graphics.Smoothness.PercentDroppedFrames.CompositorThread.Video", |
| "percent", perf.SmallerIsBetter, []int64{50, 80}, conns.BrowserTestConn), |
| } |
| } else { |
| configs = []cuj.MetricConfig{ |
| cuj.NewLatencyMetricConfig("Ash.TabDrag.PresentationTime.TabletMode"), |
| cuj.NewLatencyMetricConfig("Ash.SplitViewResize.PresentationTime.TabletMode.SingleWindow"), |
| cuj.NewLatencyMetricConfig("Ash.SplitViewResize.PresentationTime.TabletMode.WithOverview"), |
| cuj.NewLatencyMetricConfig("Ash.SplitViewResize.PresentationTime.TabletMode.MultiWindow"), |
| cuj.NewCustomMetricConfigWithTestConn( |
| "Graphics.Smoothness.PercentDroppedFrames.CompositorThread.Video", |
| "percent", perf.SmallerIsBetter, []int64{50, 80}, conns.BrowserTestConn), |
| } |
| } |
| |
| recorder, err := cuj.NewRecorder(ctx, conns.Chrome, conns.ARC, cuj.RecorderOptions{}, configs...) |
| if err != nil { |
| s.Fatal("Failed to create a recorder: ", err) |
| } |
| if testParam.Tracing { |
| recorder.EnableTracing(s.OutDir()) |
| } |
| defer recorder.Close(closeCtx) |
| |
| if err := crastestclient.Mute(ctx); err != nil { |
| s.Fatal("Failed to mute audio: ", err) |
| } |
| defer crastestclient.Unmute(closeCtx) |
| |
| defer faillog.DumpUITreeOnError(closeCtx, s.OutDir(), s.HasError, conns.TestConn) |
| |
| connPiP, err := conns.Source.NewConn(ctx, conns.PipVideoTestURL) |
| if err != nil { |
| s.Fatal("Failed to load pip.html: ", err) |
| } |
| defer connPiP.Close() |
| // Close the browser window at the end of the test. If it is left playing a video, it |
| // will cause the test server's Close() function to block for a few minutes. |
| defer connPiP.CloseTarget(closeCtx) |
| |
| if err := webutil.WaitForQuiescence(ctx, connPiP, timeout); err != nil { |
| s.Fatal("Failed to wait for pip.html to achieve quiescence: ", err) |
| } |
| |
| connNoPiP, err := conns.Source.NewConn(ctx, conns.PipVideoTestURL) |
| if err != nil { |
| s.Fatal("Failed to load pip.html: ", err) |
| } |
| defer connNoPiP.Close() |
| // Close the browser window at the end of the test. If it is left playing a video, it |
| // will cause the test server's Close() function to block for a few minutes. |
| defer connNoPiP.CloseTarget(closeCtx) |
| |
| if err := webutil.WaitForQuiescence(ctx, connNoPiP, timeout); err != nil { |
| s.Fatal("Failed to wait for pip.html to achieve quiescence: ", err) |
| } |
| |
| ui := uiauto.New(conns.TestConn) |
| |
| // Only show pip window for ash-chrome. |
| // TODO(crbug/1232492): Remove this after fix. |
| if testParam.BrowserType == browser.TypeAsh { |
| // The second tab enters the system PiP mode. |
| webview := nodewith.ClassName("ContentsWebView").Role(role.WebView) |
| pipButton := nodewith.Name("Enter Picture-in-Picture").Role(role.Button).Ancestor(webview) |
| if err := action.Combine( |
| "focus the PIP button (to ensure it is in view) and left-click on it", |
| ui.FocusAndWait(pipButton), |
| ui.LeftClick(pipButton), |
| )(ctx); err != nil { |
| s.Fatal("Failed to click the pip button: ", err) |
| } |
| if err := webutil.WaitForQuiescence(ctx, connPiP, timeout); err != nil { |
| s.Fatal("Failed to wait for quiescence: ", err) |
| } |
| } |
| |
| // Lacros specific setup. |
| if testParam.BrowserType == browser.TypeLacros { |
| // Close blank tab created at startup after creating other tabs. |
| if err := conns.CloseBlankTab(ctx); err != nil { |
| s.Fatal("Failed to close blank tab: ", err) |
| } |
| } |
| |
| var pc pointer.Context |
| if !tabletMode { |
| pc = pointer.NewMouse(conns.TestConn) |
| } else { |
| pc, err = pointer.NewTouch(ctx, conns.TestConn) |
| if err != nil { |
| s.Fatal("Failed to create a touch controller: ", err) |
| } |
| } |
| defer pc.Close() |
| |
| var f func(ctx context.Context) error |
| if !tabletMode { |
| f = func(ctx context.Context) error { |
| return windowarrangementcuj.RunClamShell(ctx, closeCtx, conns.TestConn, ui, pc, conns.ArcVideoActivity, conns.WithTestVideo) |
| } |
| } else { |
| f = func(ctx context.Context) error { |
| return windowarrangementcuj.RunTablet(ctx, closeCtx, conns.TestConn, ui, pc, conns.ArcVideoActivity, conns.WithTestVideo) |
| } |
| } |
| |
| if testParam.Validation { |
| validationHelper := cuj.NewTPSValidationHelper(closeCtx) |
| if err := validationHelper.Stress(); err != nil { |
| s.Fatal("Failed to stress: ", err) |
| } |
| defer func() { |
| if err := validationHelper.Release(); err != nil { |
| s.Fatal("Failed to release validationHelper: ", err) |
| } |
| }() |
| } |
| |
| // Run the recorder. |
| if err := recorder.Run(ctx, f); err != nil { |
| s.Fatal("Failed to conduct the recorder task: ", err) |
| } |
| |
| // Check if there is any tab crashed. |
| if err := tabChecker.Check(ctx); err != nil { |
| s.Fatal("Tab renderer crashed: ", err) |
| } |
| |
| // Store perf metrics. |
| pv := perf.NewValues() |
| if err := recorder.Record(ctx, pv); err != nil { |
| s.Fatal("Failed to record the data: ", err) |
| } |
| if err := pv.Save(s.OutDir()); err != nil { |
| s.Fatal("Failed to save the perf data: ", err) |
| } |
| } |