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