blob: 0e8ac5196d059449ae2adaa530869e656fe9c044 [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 helpapp contains common functions used in the help app.
package helpapp
import (
"context"
"fmt"
"strings"
"time"
"chromiumos/tast/errors"
"chromiumos/tast/local/apps"
"chromiumos/tast/local/chrome"
"chromiumos/tast/local/chrome/ash"
"chromiumos/tast/local/chrome/uiauto"
"chromiumos/tast/local/chrome/uiauto/nodewith"
"chromiumos/tast/local/chrome/uiauto/role"
"chromiumos/tast/local/chrome/webutil"
"chromiumos/tast/testing"
)
// HelpContext represents a context of Help app.
type HelpContext struct {
cr *chrome.Chrome
tconn *chrome.TestConn
ui *uiauto.Context
}
// NewContext creates a new context of the Help app.
func NewContext(cr *chrome.Chrome, tconn *chrome.TestConn) *HelpContext {
return &HelpContext{
ui: uiauto.New(tconn),
tconn: tconn,
cr: cr,
}
}
// RootFinder is the finder of Help app root window.
var RootFinder = nodewith.Name(apps.Help.Name).Role(role.RootWebArea)
// TabFinder is the finder of tabs in Help app.
var TabFinder = nodewith.Role(role.TreeItem).Ancestor(RootFinder)
// Tab names in Help app.
var (
SearchTabFinder = TabFinder.Name("Search")
OverviewTabFinder = TabFinder.Name("Overview")
PerksTabFinder = TabFinder.Name("Perks")
HelpTabFinder = TabFinder.Name("Help")
WhatsNewTabFinder = TabFinder.Name("See what's new")
)
var searchInputFinder = nodewith.Name("Search").Role(role.TextField).Ancestor(RootFinder)
// WaitForApp waits for the app to be shown and rendered.
// It can be slow when the app is auto-lauched after OOBE.
func (hc *HelpContext) WaitForApp() uiauto.Action {
return hc.ui.WithTimeout(time.Minute).WaitUntilExists(RootFinder)
}
// Launch launches help app and waits for it to be present in shelf.
func (hc *HelpContext) Launch() uiauto.Action {
app := apps.Help
return uiauto.Combine("launch help app",
func(ctx context.Context) error {
if err := apps.Launch(ctx, hc.tconn, app.ID); err != nil {
return errors.Wrapf(err, "failed to launch %s", app.Name)
}
testing.ContextLog(ctx, "Wait for help app shown in shelf")
if err := ash.WaitForApp(ctx, hc.tconn, app.ID, 30*time.Second); err != nil {
return errors.Wrapf(err, "%s did not appear in shelf after launch", app.Name)
}
return nil
},
hc.WaitForApp(),
)
}
// Close closes help app and waits for it to be gone from a11y tree.
func (hc *HelpContext) Close() uiauto.Action {
return func(ctx context.Context) error {
if err := apps.Close(ctx, hc.tconn, apps.Help.ID); err != nil {
return errors.Wrap(err, "failed to close the app")
}
ui := uiauto.New(hc.tconn).WithInterval(200 * time.Millisecond)
return ui.Retry(10, func(ctx context.Context) error {
isExists, err := hc.Exists(ctx)
if err != nil {
return err
} else if isExists {
return errors.New("help app still exists in a11y tree")
}
return nil
})(ctx)
}
}
// Exists checks whether the help app exists in the accessiblity tree.
func (hc *HelpContext) Exists(ctx context.Context) (bool, error) {
return hc.ui.IsNodeFound(ctx, RootFinder)
}
// UIConn returns a connection to the Help app HTML page,
// where JavaScript can be executed to simulate interactions with the UI.
// The caller should close the returned connection. e.g. defer helpAppConn.Close().
func (hc *HelpContext) UIConn(ctx context.Context) (*chrome.Conn, error) {
return hc.helpConn(ctx, "chrome-untrusted://help-app/")
}
// TrustedUIConn returns a connection to the trusted frame of Help app.
// It has more privileges to access browser functions same as other SWAs.
// The caller should close the returned connection. e.g. defer trustedConn.Close().
func (hc *HelpContext) TrustedUIConn(ctx context.Context) (*chrome.Conn, error) {
return hc.helpConn(ctx, "chrome://help-app/")
}
func (hc *HelpContext) helpConn(ctx context.Context, urlPrefix string) (*chrome.Conn, error) {
// Establish a Chrome connection to the Help app and wait for it to finish loading.
connTargetFilter := func(t *chrome.Target) bool {
return strings.HasPrefix(t.URL, urlPrefix)
}
conn, err := hc.cr.NewConnForTarget(ctx, connTargetFilter)
if err != nil {
return nil, errors.Wrap(err, "failed to get connection to help app")
}
if err := conn.WaitForExpr(ctx, `document.readyState === "complete"`); err != nil {
return nil, errors.Wrap(err, "failed to wait for help app to finish loading")
}
return conn, nil
}
// EvalJSWithShadowPiercer executes javascript in Help app web page.
func (hc *HelpContext) EvalJSWithShadowPiercer(ctx context.Context, expr string, out interface{}) error {
helpAppConn, err := hc.UIConn(ctx)
if err != nil {
return errors.Wrap(err, "failed to connect to web page")
}
defer helpAppConn.Close()
return webutil.EvalWithShadowPiercer(ctx, helpAppConn, expr, out)
}
// LoadTimeData is a struct for the help app.
// Following fields populated by |ChromeHelpAppUIDelegate::PopulateLoadTimeData|
// https://source.chromium.org/chromium/chromium/src/+/HEAD:chrome/browser/chromeos/web_applications/chrome_help_app_ui_delegate.cc;l=53;drc=c2c84a5ac7711dedcc0b7ff9e79bf7f2da019537.
type LoadTimeData struct {
IsManagedDevice bool `json:"isManagedDevice"`
}
// GetLoadTimeData returns some of the LoadTimeData fields from the help app.
func (hc *HelpContext) GetLoadTimeData(ctx context.Context) (*LoadTimeData, error) {
helpAppConn, err := hc.UIConn(ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to connect to web page")
}
defer helpAppConn.Close()
data := &LoadTimeData{}
if err := helpAppConn.Eval(ctx, "window.loadTimeData.data_", &data); err != nil {
return nil, errors.Wrap(err, "failed to evaluate window.loadTimeData.data_")
}
return data, nil
}
// IsHTMLElementPresent checks whether a HTMLElement in help app is present regardless of visibility.
// It takes cssSelector as input param and returns a bool value.
// cssSelector works piercing shadowRoot.
func (hc *HelpContext) IsHTMLElementPresent(ctx context.Context, cssSelector string) (bool, error) {
var isPresent bool
expr := fmt.Sprintf(`shadowPiercingQueryAll(%q).length>0;`, cssSelector)
if err := hc.EvalJSWithShadowPiercer(ctx, expr, &isPresent); err != nil {
return false, errors.Wrapf(err, "failed to check presence of HTML element: %s", cssSelector)
}
return isPresent, nil
}
// NavigateToPageWithURL navigates to a sub page by changing url location directly.
func (hc *HelpContext) NavigateToPageWithURL(url string, condition uiauto.Action) uiauto.Action {
return func(ctx context.Context) error {
trustedConn, err := hc.TrustedUIConn(ctx)
if err != nil {
return errors.Wrap(err, "failed to connect to help ui")
}
defer trustedConn.Close()
return webutil.NavigateToURLInApp(trustedConn, url, condition, 10*time.Second)(ctx)
}
}
// NavigateToSearchPage navigates to Search page by changing url via javascript.
func (hc *HelpContext) NavigateToSearchPage() uiauto.Action {
return hc.NavigateToPageWithURL("chrome://help-app/search", hc.ui.WithTimeout(5*time.Second).WaitUntilExists(searchInputFinder))
}
// ClickSearchInputAndWaitForActive clicks search input field and waits for active.
func (hc *HelpContext) ClickSearchInputAndWaitForActive() uiauto.Action {
return hc.ui.LeftClickUntil(searchInputFinder, hc.ui.WaitUntilExists(searchInputFinder.Focused()))
}