• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.webview.tests;
18 
19 import com.android.csuite.core.ApkInstaller;
20 import com.android.csuite.core.ApkInstaller.ApkInstallerException;
21 import com.android.csuite.core.AppCrawlTester;
22 import com.android.csuite.core.DeviceUtils;
23 import com.android.csuite.core.TestUtils;
24 import com.android.tradefed.config.Option;
25 import com.android.tradefed.device.DeviceNotAvailableException;
26 import com.android.tradefed.log.LogUtil.CLog;
27 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
28 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestLogData;
29 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
30 
31 import com.google.common.base.Preconditions;
32 
33 import org.junit.After;
34 import org.junit.Assert;
35 import org.junit.Before;
36 import org.junit.Rule;
37 import org.junit.Test;
38 import org.junit.runner.RunWith;
39 
40 import java.io.File;
41 import java.io.IOException;
42 import java.nio.file.Path;
43 import java.util.ArrayList;
44 import java.util.List;
45 
46 import javax.annotation.Nullable;
47 
48 /** A test that verifies that a single app can be successfully launched. */
49 @RunWith(DeviceJUnit4ClassRunner.class)
50 public class WebviewAppCrawlTest extends BaseHostJUnit4Test {
51     @Rule public TestLogData mLogData = new TestLogData();
52 
53     private static final String COLLECT_APP_VERSION = "collect-app-version";
54     private static final String COLLECT_GMS_VERSION = "collect-gms-version";
55     private static final long COMMAND_TIMEOUT_MILLIS = 5 * 60 * 1000;
56 
57     private WebviewUtils mWebviewUtils;
58     private WebviewPackage mPreInstalledWebview;
59     private ApkInstaller mApkInstaller;
60     private AppCrawlTester mCrawler;
61 
62     @Option(name = "record-screen", description = "Whether to record screen during test.")
63     private boolean mRecordScreen;
64 
65     @Option(name = "webview-version-to-test", description = "Version of Webview to test.")
66     private String mWebviewVersionToTest;
67 
68     @Option(
69             name = "release-channel",
70             description = "Release channel to fetch Webview from, i.e. stable.")
71     private String mReleaseChannel;
72 
73     @Option(name = "package-name", description = "Package name of testing app.")
74     private String mPackageName;
75 
76     @Option(
77             name = "install-apk",
78             description =
79                     "The path to an apk file or a directory of apk files of a singe package to be"
80                             + " installed on device. Can be repeated.")
81     private List<File> mApkPaths = new ArrayList<>();
82 
83     @Option(
84             name = "install-arg",
85             description = "Arguments for the 'adb install-multiple' package installation command.")
86     private final List<String> mInstallArgs = new ArrayList<>();
87 
88     @Option(
89             name = "app-launch-timeout-ms",
90             description = "Time to wait for an app to launch in msecs.")
91     private int mAppLaunchTimeoutMs = 20000;
92 
93     @Option(
94             name = COLLECT_APP_VERSION,
95             description =
96                     "Whether to collect package version information and store the information in"
97                             + " test log files.")
98     private boolean mCollectAppVersion;
99 
100     @Option(
101             name = COLLECT_GMS_VERSION,
102             description =
103                     "Whether to collect GMS core version information and store the information in"
104                             + " test log files.")
105     private boolean mCollectGmsVersion;
106 
107     @Option(
108             name = "repack-apk",
109             mandatory = false,
110             description =
111                     "Path to an apk file or a directory containing apk files of a single package "
112                             + "to repack and install in Espresso mode")
113     private File mRepackApk;
114 
115     @Option(
116             name = "crawl-controller-endpoint",
117             mandatory = false,
118             description = "The crawl controller endpoint to target.")
119     private String mCrawlControllerEndpoint;
120 
121     @Option(
122             name = "ui-automator-mode",
123             mandatory = false,
124             description =
125                     "Run the crawler with UIAutomator mode. Apk option is not required in this"
126                             + " mode.")
127     private boolean mUiAutomatorMode = false;
128 
129     @Option(
130             name = "robo-script-file",
131             description = "A Roboscript file to be executed by the crawler.")
132     private File mRoboscriptFile;
133 
134     // TODO(b/234512223): add support for contextual roboscript files
135 
136     @Option(
137             name = "crawl-guidance-proto-file",
138             description = "A CrawlGuidance file to be executed by the crawler.")
139     private File mCrawlGuidanceProtoFile;
140 
141     @Option(
142             name = "timeout-sec",
143             mandatory = false,
144             description = "The timeout for the crawl test.")
145     private int mTimeoutSec = 60;
146 
147     @Option(
148             name = "save-apk-when",
149             description = "When to save apk files to the test result artifacts.")
150     private TestUtils.TakeEffectWhen mSaveApkWhen = TestUtils.TakeEffectWhen.NEVER;
151 
152     @Option(
153             name = "login-config-dir",
154             description =
155                     "A directory containing Roboscript and CrawlGuidance files with login"
156                         + " credentials that are passed to the crawler. There should be one config"
157                         + " file per package name. If both Roboscript and CrawlGuidance files are"
158                         + " present, only the Roboscript file will be used.")
159     private File mLoginConfigDir;
160 
161     @Before
setUp()162     public void setUp() throws DeviceNotAvailableException, ApkInstallerException, IOException {
163         Assert.assertNotNull("Package name cannot be null", mPackageName);
164         Assert.assertTrue(
165                 "Either the --release-channel or --webview-version-to-test arguments "
166                         + "must be used",
167                 mWebviewVersionToTest != null || mReleaseChannel != null);
168 
169         mCrawler = AppCrawlTester.newInstance(mPackageName, getTestInformation(), mLogData);
170         if (!mUiAutomatorMode) {
171             setApkForEspressoMode();
172         }
173         mCrawler.setCrawlControllerEndpoint(mCrawlControllerEndpoint);
174         mCrawler.setRecordScreen(mRecordScreen);
175         mCrawler.setCollectGmsVersion(mCollectGmsVersion);
176         mCrawler.setCollectAppVersion(mCollectAppVersion);
177         mCrawler.setUiAutomatorMode(mUiAutomatorMode);
178         mCrawler.setRoboscriptFile(toPathOrNull(mRoboscriptFile));
179         mCrawler.setCrawlGuidanceProtoFile(toPathOrNull(mCrawlGuidanceProtoFile));
180         mCrawler.setLoginConfigDir(toPathOrNull(mLoginConfigDir));
181         mCrawler.setTimeoutSec(mTimeoutSec);
182 
183         mApkInstaller = ApkInstaller.getInstance(getDevice());
184         mWebviewUtils = new WebviewUtils(getTestInformation());
185         mPreInstalledWebview = mWebviewUtils.getCurrentWebviewPackage();
186 
187         for (File apkPath : mApkPaths) {
188             CLog.d("Installing " + apkPath);
189             mApkInstaller.install(apkPath.toPath(), mInstallArgs);
190         }
191 
192         DeviceUtils.getInstance(getDevice()).freezeRotation();
193         mWebviewUtils.printWebviewVersion();
194     }
195 
196     /**
197      * For Espresso mode, checks that a path with the location of the apk to repackage was provided
198      */
setApkForEspressoMode()199     private void setApkForEspressoMode() {
200         Preconditions.checkNotNull(
201                 mRepackApk, "Apk file path is required when not running in UIAutomator mode");
202         // set the root path of the target apk for Espresso mode
203         mCrawler.setApkPath(mRepackApk.toPath());
204     }
205 
toPathOrNull(@ullable File f)206     private static Path toPathOrNull(@Nullable File f) {
207         return f == null ? null : f.toPath();
208     }
209 
210     @Test
testAppCrawl()211     public void testAppCrawl()
212             throws DeviceNotAvailableException, InterruptedException, ApkInstallerException,
213                     IOException {
214         AssertionError lastError = null;
215         WebviewPackage lastWebviewInstalled =
216                 mWebviewUtils.installWebview(mWebviewVersionToTest, mReleaseChannel);
217 
218         try {
219             mCrawler.startAndAssertAppNoCrash();
220         } catch (AssertionError e) {
221             lastError = e;
222         } finally {
223             mWebviewUtils.uninstallWebview(lastWebviewInstalled, mPreInstalledWebview);
224         }
225 
226         // If the app doesn't crash, complete the test.
227         if (lastError == null) {
228             return;
229         }
230 
231         // If the app crashes, try the app with the original webview version that comes with the
232         // device.
233         try {
234             mCrawler.startAndAssertAppNoCrash();
235         } catch (AssertionError newError) {
236             CLog.w(
237                     "The app %s crashed both with and without the webview installation,"
238                             + " ignoring the failure...",
239                     mPackageName);
240             return;
241         }
242         throw new AssertionError(
243                 String.format(
244                         "Package %s crashed since webview version %s",
245                         mPackageName, lastWebviewInstalled.getVersion()),
246                 lastError);
247     }
248 
249     @After
tearDown()250     public void tearDown() throws DeviceNotAvailableException, ApkInstallerException {
251         TestUtils testUtils = TestUtils.getInstance(getTestInformation(), mLogData);
252         testUtils.collectScreenshot(mPackageName);
253 
254         DeviceUtils deviceUtils = DeviceUtils.getInstance(getDevice());
255         deviceUtils.stopPackage(mPackageName);
256         deviceUtils.unfreezeRotation();
257 
258         mApkInstaller.uninstallAllInstalledPackages();
259         mWebviewUtils.printWebviewVersion();
260 
261         if (!mUiAutomatorMode) {
262             getDevice().uninstallPackage(mPackageName);
263         }
264 
265         mCrawler.cleanUp();
266     }
267 }
268