blob: 8441d931313caf93f7302e35a5fb4f48a79e820b [file] [log] [blame]
// Copyright 2019 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 apps provides general ChromeOS app utilities.
package apps
import (
"context"
"fmt"
"time"
"chromiumos/tast/common/action"
"chromiumos/tast/errors"
"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/testing"
)
// App is used to represent a ChromeOS app.
type App struct {
// ID is the Chrome extension ID of the app.
ID string
// Name is the name of the app.
Name string
}
// Borealis App represents the installer/launcher for the borealis.
var Borealis = App{
ID: "dkecggknbdokeipkgnhifhiokailichf",
Name: "Borealis",
}
// Chat App has details about the Google Chat app.
var Chat = App{
ID: "mhihbbhgcjldimhaopinoigbbglkihll",
Name: "Google Chat",
}
// Chrome has details about the Chrome app.
var Chrome = App{
ID: "mgndgikekgjfcpckkfioiadnlibdjbkf",
Name: "Chrome",
}
// Chromium has details about the Chromium app.
// It replaces Chrome on amd64-generic builds.
var Chromium = App{
ID: "mgndgikekgjfcpckkfioiadnlibdjbkf",
Name: "Chromium",
}
// Camera has details about the Camera app.
var Camera = App{
ID: "njfbnohfdkmbmnjapinfcopialeghnmh",
Name: "Camera",
}
// Canvas has details about the Chrome Canvas app.
var Canvas = App{
ID: "ieailfmhaghpphfffooibmlghaeopach",
Name: "Chrome Canvas",
}
// Cursive has details about the Cursive app.
var Cursive = App{
ID: "apignacaigpffemhdbhmnajajaccbckh",
Name: "Cursive",
}
// ConnectivityDiagnostics has details about the Chrome Connectivity Diagnostics
// app.
var ConnectivityDiagnostics = App{
ID: "pinjbkpghjkgmlmfidajjdjocdpegjkg",
Name: "Connectivity Diagnostics",
}
// Diagnostics has details about Diagnostics SWA.
var Diagnostics = App{
ID: "keejpcfcpecjhmepmpcfgjemkmlicpam",
Name: "Diagnostics",
}
// Docs has details about the Google Docs app.
var Docs = App{
ID: "aohghmighlieiainnegkcijnfilokake",
Name: "Docs",
}
// Drive has details about the Google Drive app.
var Drive = App{
ID: "apdfllckaahabafndbhieahigkjlhalf",
Name: "Google Drive",
}
// Duo has details about the Duo app.
var Duo = App{
ID: "djkcbcmkefiiphjkonbeknmcgiheajce",
Name: "Duo",
}
// FamilyLink has details about the Family Link app.
var FamilyLink = App{
ID: "mljomdcpdfpfdplmgghfeoofmbbianlf",
Name: "Family Link",
}
// Files has details about the Files Chrome app.
var Files = App{
ID: "hhaomjibdihmijegdhdafkllkbggdgoj",
Name: "Files",
}
// FilesSWA has details about the Files System Web App.
var FilesSWA = App{
ID: "fkiggjmkendpmbegkagpmagjepfkpmeb",
Name: "Files",
}
// FirmwareUpdate has details about the FirmwareUpdate SWA.
var FirmwareUpdate = App{
ID: "nedcdcceagjbkiaecmdbpafcmlhkiifa",
Name: "Firmware Updates",
}
// Gallery (aka Backlight) has details about the Gallery app.
var Gallery = App{
ID: "jhdjimmaggjajfjphpljagpgkidjilnj",
Name: "Gallery",
}
// Gmail has details about the gmail app.
var Gmail = App{
ID: "hhkfkjpmacfncmbapfohfocpjpdnobjg",
Name: "Gmail",
}
// Help (aka Explore) has details about the Help app.
var Help = App{
ID: "nbljnnecbjbmifnoehiemkgefbnpoeak",
Name: "Explore",
}
// Lacros has details about Lacros browser app.
var Lacros = App{
ID: "jaimifaeiicidiikhmjedcgdimealfbh",
Name: "Lacros",
}
// Maps has details about Arc Maps app.
var Maps = App{
ID: "gmhipfhgnoelkiiofcnimehjnpaejiel",
Name: "Maps",
}
// Photos has details about the Photos app.
var Photos = App{
ID: "fdbkkojdbojonckghlanfaopfakedeca",
Name: "Photos",
}
// PlayBooks has details about the Play Books app.
var PlayBooks = App{
ID: "cafegjnmmjpfibnlddppihpnkbkgicbg",
Name: "Play Books",
}
// PlayGames has details about the Play Games app.
var PlayGames = App{
ID: "nplnnjkbeijcggmpdcecpabgbjgeiedc",
Name: "Play Games",
}
// PlayMovies has details about the Play Movies & TV app.
var PlayMovies = App{
ID: "dbbihmicnlldbflflckpafphlekmjfnm",
Name: "Play Movies & TV",
}
// PlayStore has details about the Play Store app.
var PlayStore = App{
ID: "cnbgggchhmkkdmeppjobngjoejnihlei",
Name: "Play Store",
}
// Calculator has details about the Calculator app.
var Calculator = App{
ID: "oabkinaljpjeilageghcdlnekhphhphl",
Name: "Calculator",
}
// Clock has details about the Clock app.
var Clock = App{
ID: "ddmmnabaeomoacfpfjgghfpocfolhjlg",
Name: "Clock",
}
// Contacts has details about the Contacts app.
var Contacts = App{
ID: "kipfkokfekalckplgaikemhghlbkgpfl",
Name: "Contacts",
}
// PrintManagement has details about the Print Management app.
var PrintManagement = App{
ID: "fglkccnmnaankjodgccmiodmlkpaiodc",
Name: "Print jobs",
}
// Scan has details about the Scan SWA.
var Scan = App{
ID: "cdkahakpgkdaoffdmfgnhgomkelkocfo",
Name: "Scan",
}
// AndroidSettings has details about ARC settings app.
var AndroidSettings = App{
ID: "mconboelelhjpkbdhhiijkgcimoangdj",
Name: "Android Settings",
}
// Settings has details about the Settings app.
var Settings = App{
ID: "odknhmnlageboeamepcngndbggdpaobj",
Name: "Settings",
}
// ShimlessRMA has details about the Shimless RMA app.
var ShimlessRMA = App{
ID: "ijolhdommgkkhpenofmpkkhlepahelcm",
Name: "Shimless RMA",
}
// TaskManager has details about the Task Manager app.
var TaskManager = App{
ID: "ijaigheoohcacdnplfbdimmcfldnnhdi",
Name: "Task Manager",
}
// Translate has details about the Translate app.
var Translate = App{
ID: "pacmnfddiadhhfmngijgjdbnodjkmojl",
Name: "Translate",
}
// TelemetryExtension has details about the TelemetryExtension app.
var TelemetryExtension = App{
ID: "lhoocnmbcmmbjgdeaallonfplogkcneb",
Name: "Telemetry Extension",
}
// Terminal has details about the Crostini Terminal app.
var Terminal = App{
ID: "fhicihalidkgcimdmhpohldehjmcabcf",
Name: "Terminal",
}
// WallpaperPicker has details about the Wallpaper Picker app.
var WallpaperPicker = App{
ID: "obklkkbkpaoaejdabbfldmcfplpdgolj",
Name: "Wallpaper Picker",
}
// WebStore has details about the WebStore app.
var WebStore = App{
ID: "ahfgeienlihckogmohjhadlkjgocpleb",
Name: "Web Store",
}
// Youtube has details about the Youtube app.
var Youtube = App{
ID: "aniolghapcdkoolpkffememnhpphmjkl",
Name: "Youtube",
}
// YouTubeCWS has details about the YouTube app from Chrome Web Store.
var YouTubeCWS = App{
ID: "blpcfgokakmgnkcojhhkbfbldkacnbeo",
Name: "YouTube",
}
// Parallels has details about the Parallels app.
var Parallels = App{
ID: "lgjpclljbbmphhnalkeplcmnjpfmmaek",
Name: "Parallels Desktop",
}
// Citrix has details about Citrix Workspace app.
var Citrix = App{
ID: "haiffjcadagjlijoggckpgfnoeiflnem",
Name: "Citrix Workspace",
}
// VMWare has details about VMware Horizon app.
var VMWare = App{
ID: "ppkfnjlimknmjoaemnpidmdlfchhehel",
Name: "VMware Horizon",
}
// Launch launches an app specified by appID.
func Launch(ctx context.Context, tconn *chrome.TestConn, appID string) error {
_, err := getInstalledAppID(ctx, tconn, func(app *ash.ChromeApp) bool { return app.AppID == appID }, nil)
if err != nil {
return err
}
return tconn.Call(ctx, nil, `tast.promisify(chrome.autotestPrivate.launchApp)`, appID)
}
func getInstalledAppID(ctx context.Context, tconn *chrome.TestConn, predicate func(*ash.ChromeApp) bool, pollOpts *testing.PollOptions) (string, error) {
appID := ""
err := testing.Poll(ctx, func(ctx context.Context) error {
capps, err := ash.ChromeApps(ctx, tconn)
if err != nil {
testing.PollBreak(err)
}
for _, capp := range capps {
if predicate(capp) {
appID = capp.AppID
return nil
}
}
return errors.New("App not yet found in available Chrome apps - have you added --enable-features=<app> to chrome options?")
}, pollOpts)
return appID, err
}
// LaunchSystemWebApp launches a system web app specifide by its name and URL.
func LaunchSystemWebApp(ctx context.Context, tconn *chrome.TestConn, appName, url string) error {
return tconn.Call(ctx, nil, `async (appName, url) => {
await tast.promisify(chrome.autotestPrivate.waitForSystemWebAppsInstall)();
await tast.promisify(chrome.autotestPrivate.launchSystemWebApp)(appName, url);
}`, appName, url)
}
// ListSystemWebApps retrieves a list of installed apps and filters down the system web apps.
func ListSystemWebApps(ctx context.Context, tconn *chrome.TestConn) ([]*ash.ChromeApp, error) {
if err := tconn.Call(ctx, nil, "tast.promisify(chrome.autotestPrivate.waitForSystemWebAppsInstall)"); err != nil {
return nil, errors.Wrap(err, "failed to wait for all system web apps to be installed")
}
chromeApps, err := ash.ChromeApps(ctx, tconn)
if err != nil {
return nil, errors.Wrap(err, "failed to get list of Chrome apps")
}
var systemWebApps []*ash.ChromeApp
for _, app := range chromeApps {
// Terminal has special handling in App Service, it has type 'Crostini' and install source 'User'.
if (app.InstallSource == "System" && app.Type == "Web") || (app.InstallSource == "User" && app.Type == "Crostini") {
systemWebApps = append(systemWebApps, app)
}
}
return systemWebApps, nil
}
// ListSystemWebAppsInternalNames returns a string[] that contains system app's internal names.
// It queries System Web App Manager via Test API.
func ListSystemWebAppsInternalNames(ctx context.Context, tconn *chrome.TestConn) ([]string, error) {
var result []string
err := tconn.Eval(
ctx,
`new Promise((resolve, reject) => {
chrome.autotestPrivate.getRegisteredSystemWebApps((system_apps) => {
if (chrome.runtime.lastError) {
reject(new Error(chrome.runtime.lastError.message));
return;
}
resolve(system_apps.map(system_app => system_app.internalName));
});
});`, &result)
if err != nil {
return nil, errors.Wrap(err, "failed to get result from Test API")
}
return result, nil
}
// LaunchOSSettings launches the OS Settings app to its subpage URL, and returns
// a connection to it. When this method returns, OS Settings page has finished
// loading.
//
// This method is necessary because OS Settings now uses System Web App link
// capturing, which doesn't work with DevTools protocol CreateTarget.
//
// Note, `url` needs to exactly match the page OS Settings ends up navigating to.
// For example, chrome://os-settings/.
func LaunchOSSettings(ctx context.Context, cr *chrome.Chrome, url string) (*chrome.Conn, error) {
ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
tconn, err := cr.TestAPIConn(ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to connect Test API")
}
if LaunchSystemWebApp(ctx, tconn, "OSSettings", url); err != nil {
return nil, errors.Wrap(err, "failed to launch OS Settings")
}
conn, err := cr.NewConnForTarget(ctx, chrome.MatchTargetURL(url))
if err != nil {
return nil, errors.Wrap(err, "failed to get connection to OS Settings")
}
if conn.WaitForExpr(ctx, "document.readyState === 'complete'"); err != nil {
return nil, errors.Wrap(err, "failed to wait for document load")
}
return conn, nil
}
// Close closes an app specified by appID.
func Close(ctx context.Context, tconn *chrome.TestConn, appID string) error {
return tconn.Call(ctx, nil, `tast.promisify(chrome.autotestPrivate.closeApp)`, appID)
}
// ChromeOrChromium returns the correct browser for the current build.
// Chromium is returned on non branded builds (e.g amd64-generic).
func ChromeOrChromium(ctx context.Context, tconn *chrome.TestConn) (App, error) {
capps, err := ash.ChromeApps(ctx, tconn)
if err != nil {
return App{}, errors.Wrap(err, "failed to get list of installed apps")
}
for _, app := range capps {
if app.AppID == Chrome.ID {
if app.Name == Chrome.Name {
return Chrome, nil
}
return Chromium, nil
}
}
return App{}, errors.New("Neither Chrome nor Chromium were found in available apps")
}
// PrimaryBrowser returns the primary browser for the current system configuration.
// In LacrosPrimary and LacrosOnly configurations, this is 'Lacros'.
// Otherwise it is 'Chrome' or 'Chromium' depending on branding.
// The given TestConn must be a connection to Ash.
func PrimaryBrowser(ctx context.Context, tconn *chrome.TestConn) (App, error) {
const js = "tast.promisify(chrome.autotestPrivate.isLacrosPrimaryBrowser)()"
var lacros bool
if err := tconn.Eval(ctx, js, &lacros); err != nil {
return App{}, errors.Wrap(err, "failed to call isLacrosPrimaryBrowser")
}
if lacros {
return Lacros, nil
}
browserApp, err := ChromeOrChromium(ctx, tconn)
if err != nil {
return App{}, errors.Wrap(err, "failed to find the browser app for ash-chrome")
}
return browserApp, nil
}
// InstallPWAForURL navigates to a PWA, attempts to install and returns the installed app ID.
func InstallPWAForURL(ctx context.Context, cr *chrome.Chrome, pwaURL string, timeout time.Duration) (string, error) {
conn, err := cr.NewConn(ctx, pwaURL)
if err != nil {
return "", errors.Wrapf(err, "failed to open URL %q", pwaURL)
}
defer conn.Close()
tconn, err := cr.TestAPIConn(ctx)
if err != nil {
return "", errors.Wrap(err, "failed to connect to test API")
}
// The installability checks occur asynchronously for PWAs.
// Wait for the Install button to appear in the Chrome omnibox before installing.
ui := uiauto.New(tconn)
install := nodewith.ClassName("PwaInstallView").Role(role.Button)
if err := ui.WithTimeout(timeout).WaitUntilExists(install)(ctx); err != nil {
return "", errors.Wrap(err, "failed to wait for the install button in the omnibox")
}
evalString := fmt.Sprintf("tast.promisify(chrome.autotestPrivate.installPWAForCurrentURL)(%d)", timeout.Milliseconds())
var appID string
if err := tconn.Eval(ctx, evalString, &appID); err != nil {
return "", errors.Wrap(err, "failed to run installPWAForCurrentURL")
}
return appID, nil
}
// LaunchChromeByShortcut launches a new Chrome window in either normal user mode by shortcut `Ctl+N`
// or incognito mode by shortcut `Ctl+Shift+N`.
func LaunchChromeByShortcut(tconn *chrome.TestConn, incognitoMode bool) action.Action {
return func(ctx context.Context) error {
return tconn.Call(ctx, nil, `async (incognito) => {
let accelerator = {keyCode: 'n', shift: incognito, control: true, alt: false, search: false, pressed: true};
await tast.promisify(chrome.autotestPrivate.activateAccelerator)(accelerator);
accelerator.pressed = false;
await tast.promisify(chrome.autotestPrivate.activateAccelerator)(accelerator);
}`, incognitoMode)
}
}