1 // Copyright 2020 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 package org.chromium.android_webview.test.devui;
6 
7 import static androidx.test.espresso.Espresso.onData;
8 import static androidx.test.espresso.Espresso.onView;
9 import static androidx.test.espresso.action.ViewActions.click;
10 import static androidx.test.espresso.action.ViewActions.longClick;
11 import static androidx.test.espresso.assertion.ViewAssertions.matches;
12 import static androidx.test.espresso.intent.Intents.assertNoUnverifiedIntents;
13 import static androidx.test.espresso.intent.Intents.intended;
14 import static androidx.test.espresso.intent.Intents.intending;
15 import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
16 import static androidx.test.espresso.matcher.ViewMatchers.withId;
17 import static androidx.test.espresso.matcher.ViewMatchers.withText;
18 
19 import static org.hamcrest.MatcherAssert.assertThat;
20 import static org.hamcrest.Matchers.anything;
21 import static org.hamcrest.Matchers.equalTo;
22 import static org.hamcrest.Matchers.is;
23 import static org.hamcrest.Matchers.not;
24 
25 import static org.chromium.android_webview.test.devui.DeveloperUiTestUtils.getClipBoardTextOnUiThread;
26 import static org.chromium.android_webview.test.devui.DeveloperUiTestUtils.withCount;
27 
28 import android.app.Activity;
29 import android.app.Instrumentation.ActivityResult;
30 import android.content.Context;
31 import android.content.Intent;
32 import android.content.pm.PackageInfo;
33 import android.os.Build;
34 import android.provider.Settings;
35 import android.support.test.InstrumentationRegistry;
36 
37 import androidx.test.espresso.intent.matcher.IntentMatchers;
38 import androidx.test.espresso.intent.rule.IntentsTestRule;
39 import androidx.test.filters.MediumTest;
40 
41 import org.junit.After;
42 import org.junit.Assume;
43 import org.junit.Rule;
44 import org.junit.Test;
45 import org.junit.runner.RunWith;
46 
47 import org.chromium.android_webview.devui.MainActivity;
48 import org.chromium.android_webview.devui.R;
49 import org.chromium.android_webview.devui.WebViewPackageError;
50 import org.chromium.android_webview.devui.util.WebViewPackageHelper;
51 import org.chromium.android_webview.test.AwJUnit4ClassRunner;
52 import org.chromium.base.test.util.Batch;
53 import org.chromium.base.test.util.Feature;
54 
55 import java.util.Locale;
56 
57 /**
58  * UI tests for the developer UI's HomeFragment.
59  */
60 @RunWith(AwJUnit4ClassRunner.class)
61 @Batch(Batch.PER_CLASS)
62 public class HomeFragmentTest {
63     public static final PackageInfo FAKE_WEBVIEW_PACKAGE = new PackageInfo();
64     static {
65         FAKE_WEBVIEW_PACKAGE.packageName = "org.chromium.fake_webview";
66         FAKE_WEBVIEW_PACKAGE.versionCode = 123456789;
67         FAKE_WEBVIEW_PACKAGE.versionName = "999.888.777.666";
68     }
69 
70     @Rule
71     public IntentsTestRule mRule =
72             new IntentsTestRule<MainActivity>(MainActivity.class, false, false);
73 
74     @After
tearDown()75     public void tearDown() {
76         // Activity is launched, i.e the test is not skipped.
77         if (mRule.getActivity() != null) {
78             // Tests are responsible for verifying every Intent they trigger.
79             assertNoUnverifiedIntents();
80         }
81     }
82 
launchHomeFragment()83     private void launchHomeFragment() {
84         Intent intent = new Intent();
85         intent.putExtra(MainActivity.FRAGMENT_ID_INTENT_EXTRA, MainActivity.FRAGMENT_ID_HOME);
86         mRule.launchActivity(intent);
87 
88         // Stub all external intents, to avoid launching other apps (ex. system browser), has to be
89         // done after launching the activity.
90         intending(not(IntentMatchers.isInternal()))
91                 .respondWith(new ActivityResult(Activity.RESULT_OK, null));
92     }
93 
94     @Test
95     @MediumTest
96     @Feature({"AndroidWebView"})
97     // Test when the system WebView provider is the same package from which the developer UI is
98     // launched.
testSameWebViewPackage()99     public void testSameWebViewPackage() throws Throwable {
100         Context context = InstrumentationRegistry.getTargetContext();
101         // Inject test app package as the current WebView package.
102         WebViewPackageHelper.setCurrentWebViewPackageForTesting(
103                 WebViewPackageHelper.getContextPackageInfo(context));
104         launchHomeFragment();
105 
106         // No error messages is displayed.
107         onView(withId(R.id.main_error_view)).check(matches(not(isDisplayed())));
108 
109         onView(withId(R.id.main_info_list)).check(matches(withCount(2)));
110 
111         PackageInfo currentWebViewPackage = WebViewPackageHelper.getCurrentWebViewPackage(context);
112         String expectedWebViewPackageInfo =
113                 String.format(Locale.US, "%s (%s/%s)", currentWebViewPackage.packageName,
114                         currentWebViewPackage.versionName, currentWebViewPackage.versionCode);
115         onData(anything())
116                 .atPosition(0)
117                 .onChildView(withId(android.R.id.text1))
118                 .check(matches(withText("WebView package")));
119         onData(anything())
120                 .atPosition(0)
121                 .onChildView(withId(android.R.id.text2))
122                 .check(matches(withText(expectedWebViewPackageInfo)));
123 
124         String expectedDeviceInfo =
125                 String.format(Locale.US, "%s - %s", Build.MODEL, Build.FINGERPRINT);
126         onData(anything())
127                 .atPosition(1)
128                 .onChildView(withId(android.R.id.text1))
129                 .check(matches(withText("Device info")));
130         onData(anything())
131                 .atPosition(1)
132                 .onChildView(withId(android.R.id.text2))
133                 .check(matches(withText(expectedDeviceInfo)));
134     }
135 
136     @Test
137     @MediumTest
138     @Feature({"AndroidWebView"})
139     // Test when the system WebView provider is different from the package from which the developer
140     // UI is launched.
testDifferentWebViewPackage()141     public void testDifferentWebViewPackage() throws Throwable {
142         Context context = InstrumentationRegistry.getTargetContext();
143         // Inject a dummy PackageInfo as the current WebView package to make sure it will always be
144         // different from the test's app package.
145         WebViewPackageHelper.setCurrentWebViewPackageForTesting(FAKE_WEBVIEW_PACKAGE);
146         launchHomeFragment();
147 
148         onView(withId(R.id.main_info_list)).check(matches(withCount(3)));
149 
150         String expectedWebViewPackageInfo =
151                 String.format(Locale.US, "%s (%s/%s)", FAKE_WEBVIEW_PACKAGE.packageName,
152                         FAKE_WEBVIEW_PACKAGE.versionName, FAKE_WEBVIEW_PACKAGE.versionCode);
153         onData(anything())
154                 .atPosition(0)
155                 .onChildView(withId(android.R.id.text1))
156                 .check(matches(withText("WebView package")));
157         onData(anything())
158                 .atPosition(0)
159                 .onChildView(withId(android.R.id.text2))
160                 .check(matches(withText(expectedWebViewPackageInfo)));
161 
162         PackageInfo devUiPackage = WebViewPackageHelper.getContextPackageInfo(context);
163         String expectedDevUiInfo = String.format(Locale.US, "%s (%s/%s)", devUiPackage.packageName,
164                 devUiPackage.versionName, devUiPackage.versionCode);
165         onData(anything())
166                 .atPosition(1)
167                 .onChildView(withId(android.R.id.text1))
168                 .check(matches(withText("DevTools package")));
169         onData(anything())
170                 .atPosition(1)
171                 .onChildView(withId(android.R.id.text2))
172                 .check(matches(withText(expectedDevUiInfo)));
173 
174         String expectedDeviceInfo =
175                 String.format(Locale.US, "%s - %s", Build.MODEL, Build.FINGERPRINT);
176         onData(anything())
177                 .atPosition(2)
178                 .onChildView(withId(android.R.id.text1))
179                 .check(matches(withText("Device info")));
180         onData(anything())
181                 .atPosition(2)
182                 .onChildView(withId(android.R.id.text2))
183                 .check(matches(withText(expectedDeviceInfo)));
184     }
185 
186     @Test
187     @MediumTest
188     @Feature({"AndroidWebView"})
testLongPressCopy()189     public void testLongPressCopy() throws Throwable {
190         Context context = InstrumentationRegistry.getTargetContext();
191         // Inject a dummy PackageInfo as the current WebView package to make sure it will always be
192         // different from the test's app package.
193         WebViewPackageHelper.setCurrentWebViewPackageForTesting(FAKE_WEBVIEW_PACKAGE);
194         launchHomeFragment();
195 
196         onView(withText("WebView package")).perform(longClick());
197         String expectedWebViewInfo =
198                 String.format(Locale.US, "%s (%s/%s)", FAKE_WEBVIEW_PACKAGE.packageName,
199                         FAKE_WEBVIEW_PACKAGE.versionName, FAKE_WEBVIEW_PACKAGE.versionCode);
200         assertThat(getClipBoardTextOnUiThread(context), is(equalTo(expectedWebViewInfo)));
201 
202         onView(withText("DevTools package")).perform(longClick());
203         PackageInfo devUiPackage = WebViewPackageHelper.getContextPackageInfo(context);
204         String expectedDevUiInfo = String.format(Locale.US, "%s (%s/%s)", devUiPackage.packageName,
205                 devUiPackage.versionName, devUiPackage.versionCode);
206         assertThat(getClipBoardTextOnUiThread(context), is(equalTo(expectedDevUiInfo)));
207 
208         onView(withText("Device info")).perform(longClick());
209         String expectedDeviceInfo =
210                 String.format(Locale.US, "%s - %s", Build.MODEL, Build.FINGERPRINT);
211         assertThat(getClipBoardTextOnUiThread(context), is(equalTo(expectedDeviceInfo)));
212     }
213 
214     @Test
215     @MediumTest
216     @Feature({"AndroidWebView"})
testDifferentWebViewPackageError_bannerMessage_postNougat()217     public void testDifferentWebViewPackageError_bannerMessage_postNougat() throws Throwable {
218         Assume.assumeTrue("This test verifies behavior introduced in Nougat and above",
219                 Build.VERSION.SDK_INT >= Build.VERSION_CODES.N);
220 
221         Context context = InstrumentationRegistry.getTargetContext();
222         // Inject a dummy PackageInfo as the current WebView package to make sure it will always be
223         // different from the test's app package.
224         WebViewPackageHelper.setCurrentWebViewPackageForTesting(FAKE_WEBVIEW_PACKAGE);
225         launchHomeFragment();
226 
227         String expectedErrorMessage = String.format(Locale.US,
228                 WebViewPackageError.DIFFERENT_WEBVIEW_PROVIDER_ERROR_MESSAGE,
229                 WebViewPackageHelper.loadLabel(context));
230         onView(withId(R.id.main_error_view)).check(matches(isDisplayed()));
231         onView(withId(R.id.error_text)).check(matches(withText(expectedErrorMessage)));
232         // Since the current provider is set to a fake package not an actual installed WebView
233         // provider, the UI should only offer to change the system WebView provider and should not
234         // offer to open the current WebView provider dev UI.
235         onView(withId(R.id.action_button))
236                 .check(matches(withText(WebViewPackageError.CHANGE_WEBVIEW_PROVIDER_BUTTON_TEXT)))
237                 .perform(click());
238         intended(IntentMatchers.hasAction(Settings.ACTION_WEBVIEW_SETTINGS));
239     }
240 
241     @Test
242     @MediumTest
243     @Feature({"AndroidWebView"})
244     // Test the dialog shown when the WebView package error message is clicked.
testDifferentWebViewPackageError_dialog_postNougat()245     public void testDifferentWebViewPackageError_dialog_postNougat() throws Throwable {
246         Assume.assumeTrue("This test verifies behavior introduced in Nougat and above",
247                 Build.VERSION.SDK_INT >= Build.VERSION_CODES.N);
248 
249         Context context = InstrumentationRegistry.getTargetContext();
250         // Inject a dummy PackageInfo as the current WebView package to make sure it will always be
251         // different from the test's app package.
252         WebViewPackageHelper.setCurrentWebViewPackageForTesting(FAKE_WEBVIEW_PACKAGE);
253         launchHomeFragment();
254 
255         String dialogExpectedMessage = String.format(Locale.US,
256                 WebViewPackageError.DIFFERENT_WEBVIEW_PROVIDER_DIALOG_MESSAGE,
257                 WebViewPackageHelper.loadLabel(context));
258         onView(withId(R.id.main_error_view)).perform(click());
259         onView(withText(dialogExpectedMessage)).check(matches(isDisplayed()));
260         // Since the current provider is set to a fake package not an actual installed WebView
261         // provider, the UI should only offer to change the system WebView provider and should not
262         // offer to open the current WebView provider dev UI.
263         onView(withId(android.R.id.button1)).check(matches(not(isDisplayed()))); // positive button
264         onView(withId(android.R.id.button2)).check(matches(not(isDisplayed()))); // negative button
265         // botton3 is dialog neutral button
266         onView(withId(android.R.id.button3))
267                 .check(matches(withText(WebViewPackageError.CHANGE_WEBVIEW_PROVIDER_BUTTON_TEXT)))
268                 .perform(click());
269         intended(IntentMatchers.hasAction(Settings.ACTION_WEBVIEW_SETTINGS));
270     }
271 
272     @Test
273     @MediumTest
274     @Feature({"AndroidWebView"})
275     // Test that error message is shown when system's WebView provider package is different from dev
276     // UI's on a preNougat android versions (where WebView provider can't be changed).
testDifferentWebViewPackageError_bannerMessage_preNougat()277     public void testDifferentWebViewPackageError_bannerMessage_preNougat() throws Throwable {
278         Assume.assumeTrue("This test verifies pre-Nougat behavior",
279                 Build.VERSION.SDK_INT < Build.VERSION_CODES.N);
280 
281         Context context = InstrumentationRegistry.getTargetContext();
282         // Inject a dummy PackageInfo as the current WebView package to make sure it will always be
283         // different from the test's app package.
284         WebViewPackageHelper.setCurrentWebViewPackageForTesting(FAKE_WEBVIEW_PACKAGE);
285         launchHomeFragment();
286 
287         String expectedErrorMessage = String.format(Locale.US,
288                 WebViewPackageError.DIFFERENT_WEBVIEW_PROVIDER_ERROR_MESSAGE,
289                 WebViewPackageHelper.loadLabel(context));
290         onView(withId(R.id.main_error_view)).check(matches(isDisplayed()));
291         onView(withId(R.id.error_text)).check(matches(withText(expectedErrorMessage)));
292         // Since the current provider is set to a fake package not an actual installed WebView
293         // provider, the UI shouldn't offer opening current WebView provider dev UI. It should not
294         // offer to change system WebView provider because this is not supported on pre-Nougat
295         // android versions.
296         onView(withId(R.id.action_button)).check(matches(not(isDisplayed())));
297     }
298 
299     @Test
300     @MediumTest
301     @Feature({"AndroidWebView"})
302     // Test the dialog shown when the WebView package error message is clicked (where WebView
303     // provider can't be changed).
testDifferentWebViewPackageError_dialog_preNougat()304     public void testDifferentWebViewPackageError_dialog_preNougat() throws Throwable {
305         Assume.assumeTrue("This test verifies pre-Nougat behavior",
306                 Build.VERSION.SDK_INT < Build.VERSION_CODES.N);
307 
308         Context context = InstrumentationRegistry.getTargetContext();
309         // Inject a dummy PackageInfo as the current WebView package to make sure it will always be
310         // different from the test's app package.
311         WebViewPackageHelper.setCurrentWebViewPackageForTesting(FAKE_WEBVIEW_PACKAGE);
312         launchHomeFragment();
313 
314         String dialogExpectedMessage = String.format(Locale.US,
315                 WebViewPackageError.DIFFERENT_WEBVIEW_PROVIDER_DIALOG_MESSAGE,
316                 WebViewPackageHelper.loadLabel(context));
317         onView(withId(R.id.main_error_view)).perform(click());
318         onView(withText(dialogExpectedMessage)).check(matches(isDisplayed()));
319         // Since the current provider is set to a fake package not an actual installed WebView
320         // provider, the UI shouldn't offer opening current WebView provider dev UI. It should not
321         // offer to change system WebView provider because this is not supported on pre-Nougat
322         // android versions.
323         //
324         // There should be no buttons in the Dialog.
325         onView(withId(android.R.id.button1)).check(matches(not(isDisplayed())));
326         onView(withId(android.R.id.button2)).check(matches(not(isDisplayed())));
327         onView(withId(android.R.id.button3)).check(matches(not(isDisplayed())));
328     }
329 }
330