• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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.DeviceUtils;
22 import com.android.csuite.core.DeviceUtils.DeviceTimestamp;
23 import com.android.csuite.core.DeviceUtils.DeviceUtilsException;
24 import com.android.csuite.core.TestUtils;
25 import com.android.tradefed.config.Option;
26 import com.android.tradefed.config.Option.Importance;
27 import com.android.tradefed.device.DeviceNotAvailableException;
28 import com.android.tradefed.log.LogUtil.CLog;
29 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
30 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestLogData;
31 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
32 import com.android.tradefed.util.AaptParser;
33 import com.android.tradefed.util.CommandResult;
34 import com.android.tradefed.util.CommandStatus;
35 import com.android.tradefed.util.RunUtil;
36 
37 import org.junit.After;
38 import org.junit.Assert;
39 import org.junit.Before;
40 import org.junit.Rule;
41 import org.junit.Test;
42 import org.junit.runner.RunWith;
43 
44 import java.io.File;
45 import java.io.IOException;
46 import java.util.ArrayList;
47 import java.util.Arrays;
48 import java.util.Collections;
49 import java.util.Comparator;
50 import java.util.List;
51 import java.util.regex.Matcher;
52 import java.util.regex.Pattern;
53 
54 /** A test that verifies that a single app can be successfully launched. */
55 @RunWith(DeviceJUnit4ClassRunner.class)
56 public class WebviewAppLaunchTest extends BaseHostJUnit4Test {
57     @Rule public TestLogData mLogData = new TestLogData();
58     private ApkInstaller mApkInstaller;
59     private List<File> mOrderedWebviewApks = new ArrayList<>();
60 
61     @Option(name = "record-screen", description = "Whether to record screen during test.")
62     private boolean mRecordScreen;
63 
64     @Option(name = "package-name", description = "Package name of testing app.")
65     private String mPackageName;
66 
67     @Option(
68             name = "install-apk",
69             description =
70                     "The path to an apk file or a directory of apk files of a singe package to be"
71                             + " installed on device. Can be repeated.")
72     private List<File> mApkPaths = new ArrayList<>();
73 
74     @Option(
75             name = "install-arg",
76             description = "Arguments for the 'adb install-multiple' package installation command.")
77     private final List<String> mInstallArgs = new ArrayList<>();
78 
79     @Option(
80             name = "app-launch-timeout-ms",
81             description = "Time to wait for an app to launch in msecs.")
82     private int mAppLaunchTimeoutMs = 20000;
83 
84     @Option(
85             name = "webview-apk-dir",
86             description = "The path to the webview apk.",
87             importance = Importance.ALWAYS)
88     private File mWebviewApkDir;
89 
90     @Before
setUp()91     public void setUp() throws DeviceNotAvailableException, ApkInstallerException, IOException {
92         Assert.assertNotNull("Package name cannot be null", mPackageName);
93 
94         readWebviewApkDirectory();
95 
96         mApkInstaller = ApkInstaller.getInstance(getDevice());
97         for (File apkPath : mApkPaths) {
98             CLog.d("Installing " + apkPath);
99             mApkInstaller.install(
100                     apkPath.toPath(), mInstallArgs.toArray(new String[mInstallArgs.size()]));
101         }
102 
103         DeviceUtils deviceUtils = DeviceUtils.getInstance(getDevice());
104         deviceUtils.freezeRotation();
105 
106         printWebviewVersion();
107     }
108 
109     @Test
testAppLaunch()110     public void testAppLaunch()
111             throws DeviceNotAvailableException, ApkInstallerException, IOException {
112         AssertionError lastError = null;
113         // Try the latest webview version
114         WebviewPackage lastWebviewInstalled = installWebview(mOrderedWebviewApks.get(0));
115         try {
116             assertAppLaunchNoCrash();
117         } catch (AssertionError e) {
118             lastError = e;
119         } finally {
120             uninstallWebview();
121         }
122 
123         // If the app doesn't crash, complete the test.
124         if (lastError == null) {
125             return;
126         }
127 
128         // If the app crashes, try the app with the original webview version that comes with the
129         // device.
130         try {
131             assertAppLaunchNoCrash();
132         } catch (AssertionError newError) {
133             CLog.w(
134                     "The app %s crashed both with and without the webview installation,"
135                             + " ignoring the failure...",
136                     mPackageName);
137             return;
138         }
139 
140         for (int idx = 1; idx < mOrderedWebviewApks.size(); idx++) {
141             lastWebviewInstalled = installWebview(mOrderedWebviewApks.get(idx));
142             try {
143                 assertAppLaunchNoCrash();
144             } catch (AssertionError e) {
145                 lastError = e;
146                 continue;
147             } finally {
148                 uninstallWebview();
149             }
150             break;
151         }
152 
153         throw new AssertionError(
154                 String.format(
155                         "Package %s crashed since webview version %s",
156                         mPackageName, lastWebviewInstalled.getVersion()),
157                 lastError);
158     }
159 
160     @After
tearDown()161     public void tearDown() throws DeviceNotAvailableException, ApkInstallerException {
162         TestUtils testUtils = TestUtils.getInstance(getTestInformation(), mLogData);
163         testUtils.collectScreenshot(mPackageName);
164 
165         DeviceUtils deviceUtils = DeviceUtils.getInstance(getDevice());
166         deviceUtils.stopPackage(mPackageName);
167         deviceUtils.unfreezeRotation();
168 
169         mApkInstaller.uninstallAllInstalledPackages();
170         printWebviewVersion();
171     }
172 
readWebviewApkDirectory()173     private void readWebviewApkDirectory() {
174         mOrderedWebviewApks = Arrays.asList(mWebviewApkDir.listFiles());
175         Collections.sort(
176                 mOrderedWebviewApks,
177                 new Comparator<File>() {
178                     @Override
179                     public int compare(File apk1, File apk2) {
180                         return getVersionCode(apk2).compareTo(getVersionCode(apk1));
181                     }
182 
183                     private Long getVersionCode(File apk) {
184                         return Long.parseLong(AaptParser.parse(apk).getVersionCode());
185                     }
186                 });
187     }
188 
printWebviewVersion(WebviewPackage currentWebview)189     private void printWebviewVersion(WebviewPackage currentWebview)
190             throws DeviceNotAvailableException {
191         CLog.i("Current webview implementation: %s", currentWebview.getPackageName());
192         CLog.i("Current webview version: %s", currentWebview.getVersion());
193     }
194 
printWebviewVersion()195     private void printWebviewVersion() throws DeviceNotAvailableException {
196         WebviewPackage currentWebview = getCurrentWebviewPackage();
197         printWebviewVersion(currentWebview);
198     }
199 
installWebview(File apk)200     private WebviewPackage installWebview(File apk)
201             throws ApkInstallerException, IOException, DeviceNotAvailableException {
202         ApkInstaller.getInstance(getDevice()).install(apk.toPath());
203         CommandResult res =
204                 getDevice()
205                         .executeShellV2Command(
206                                 "cmd webviewupdate set-webview-implementation com.android.webview");
207         Assert.assertEquals(
208                 "Failed to set webview update: " + res, res.getStatus(), CommandStatus.SUCCESS);
209         WebviewPackage currentWebview = getCurrentWebviewPackage();
210         printWebviewVersion(currentWebview);
211         return currentWebview;
212     }
213 
uninstallWebview()214     private void uninstallWebview() throws DeviceNotAvailableException {
215         getDevice()
216                 .executeShellCommand(
217                         "cmd webviewupdate set-webview-implementation com.google.android.webview");
218         getDevice().executeAdbCommand("uninstall", "com.android.webview");
219     }
220 
getCurrentWebviewPackage()221     private WebviewPackage getCurrentWebviewPackage() throws DeviceNotAvailableException {
222         String dumpsys = getDevice().executeShellCommand("dumpsys webviewupdate");
223         return WebviewPackage.parseFrom(dumpsys);
224     }
225 
226     private static class WebviewPackage {
227         private final String mPackageName;
228         private final String mVersion;
229 
WebviewPackage(String packageName, String version)230         private WebviewPackage(String packageName, String version) {
231             mPackageName = packageName;
232             mVersion = version;
233         }
234 
parseFrom(String dumpsys)235         static WebviewPackage parseFrom(String dumpsys) {
236             Pattern pattern =
237                     Pattern.compile("Current WebView package \\(name, version\\): \\((.*?)\\)");
238             Matcher matcher = pattern.matcher(dumpsys);
239             Assert.assertTrue("Cannot parse webview package info from: " + dumpsys, matcher.find());
240             String[] packageInfo = matcher.group(1).split(",");
241             return new WebviewPackage(packageInfo[0].strip(), packageInfo[1].strip());
242         }
243 
getPackageName()244         String getPackageName() {
245             return mPackageName;
246         }
247 
getVersion()248         String getVersion() {
249             return mVersion;
250         }
251     }
252 
assertAppLaunchNoCrash()253     private void assertAppLaunchNoCrash() throws DeviceNotAvailableException {
254         DeviceUtils deviceUtils = DeviceUtils.getInstance(getDevice());
255         deviceUtils.resetPackage(mPackageName);
256         TestUtils testUtils = TestUtils.getInstance(getTestInformation(), mLogData);
257 
258         if (mRecordScreen) {
259             testUtils.collectScreenRecord(
260                     () -> {
261                         launchPackageAndCheckForCrash();
262                     },
263                     mPackageName);
264         } else {
265             launchPackageAndCheckForCrash();
266         }
267     }
268 
launchPackageAndCheckForCrash()269     private void launchPackageAndCheckForCrash() throws DeviceNotAvailableException {
270         CLog.d("Launching package: %s.", mPackageName);
271 
272         DeviceUtils deviceUtils = DeviceUtils.getInstance(getDevice());
273         TestUtils testUtils = TestUtils.getInstance(getTestInformation(), mLogData);
274 
275         DeviceTimestamp startTime = deviceUtils.currentTimeMillis();
276         try {
277             deviceUtils.launchPackage(mPackageName);
278         } catch (DeviceUtilsException e) {
279             Assert.fail(e.getMessage());
280         }
281 
282         CLog.d("Waiting %s milliseconds for the app to launch fully.", mAppLaunchTimeoutMs);
283         RunUtil.getDefault().sleep(mAppLaunchTimeoutMs);
284 
285         CLog.d("Completed launching package: %s", mPackageName);
286 
287         try {
288             String crashLog = testUtils.getDropboxPackageCrashLog(mPackageName, startTime, true);
289             if (crashLog != null) {
290                 Assert.fail(crashLog);
291             }
292         } catch (IOException e) {
293             Assert.fail("Error while getting dropbox crash log: " + e);
294         }
295     }
296 }
297