1 /*
2  * Copyright (C) 2020 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 android.autofillservice.cts.inline;
18 
19 import static android.autofillservice.cts.activities.WebViewActivity.HTML_NAME_PASSWORD;
20 import static android.autofillservice.cts.activities.WebViewActivity.HTML_NAME_USERNAME;
21 import static android.autofillservice.cts.testcore.InstrumentedAutoFillServiceInlineEnabled.SERVICE_NAME;
22 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
23 
24 import static com.google.common.truth.Truth.assertThat;
25 
26 import android.app.assist.AssistStructure.ViewNode;
27 import android.autofillservice.cts.activities.MyWebView;
28 import android.autofillservice.cts.activities.WebViewActivity;
29 import android.autofillservice.cts.commontests.AbstractWebViewTestCase;
30 import android.autofillservice.cts.testcore.AutofillActivityTestRule;
31 import android.autofillservice.cts.testcore.CannedFillResponse;
32 import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
33 import android.autofillservice.cts.testcore.Helper;
34 import android.autofillservice.cts.testcore.InlineUiBot;
35 import android.autofillservice.cts.testcore.InstrumentedAutoFillService.FillRequest;
36 import android.autofillservice.cts.testcore.InstrumentedAutoFillService.SaveRequest;
37 import android.util.Log;
38 import android.view.KeyEvent;
39 import android.view.ViewStructure.HtmlInfo;
40 
41 import androidx.test.filters.FlakyTest;
42 import androidx.test.uiautomator.UiObject2;
43 
44 import org.junit.Assume;
45 import org.junit.Test;
46 import org.junit.rules.TestRule;
47 
48 @FlakyTest(bugId = 162372863)
49 public class InlineWebViewActivityTest extends AbstractWebViewTestCase<WebViewActivity> {
50 
51     private static final String TAG = "InlineWebViewActivityTest";
52     private WebViewActivity mActivity;
53 
InlineWebViewActivityTest()54     public InlineWebViewActivityTest() {
55         super(getInlineUiBot());
56     }
57 
58     @Override
getActivityRule()59     protected AutofillActivityTestRule<WebViewActivity> getActivityRule() {
60         return new AutofillActivityTestRule<WebViewActivity>(WebViewActivity.class) {
61             @Override
62             protected void beforeActivityLaunched() {
63                 super.beforeActivityLaunched();
64                 Log.i(TAG, "Setting service before launching the activity");
65                 enableService();
66             }
67 
68             @Override
69             protected void afterActivityLaunched() {
70                 mActivity = getActivity();
71             }
72         };
73     }
74 
75     @Override
76     protected void enableService() {
77         Helper.enableAutofillService(SERVICE_NAME);
78     }
79 
80     @Override
81     protected boolean isInlineMode() {
82         return true;
83     }
84 
85     @Override
86     public TestRule getMainTestRule() {
87         return InlineUiBot.annotateRule(super.getMainTestRule());
88     }
89 
90     @Test
91     public void testAutofillNoDatasets() throws Exception {
92         // Set service.
93         enableService();
94 
95         // Load WebView
96         mActivity.loadWebView(mUiBot);
97 
98         // Set expectations.
99         sReplier.addResponse(CannedFillResponse.NO_RESPONSE);
100 
101         // Trigger autofill.
102         mActivity.getUsernameInput().click();
103         sReplier.getNextFillRequest();
104 
105         // Assert not shown.
106         mUiBot.assertNoDatasetsEver();
107     }
108 
109     @Test
110     public void testAutofillOneDataset() throws Exception {
111         // TODO(b/226240255) WebView does not inform the autofill service about editText (re-)entry
112         Assume.assumeFalse(isPreventImeStartup());
113 
114         // TODO(b/187664861): Find better solution for small display device.
115         mUiBot.assumeMinimumResolution(500);
116         // Set service.
117         enableService();
118 
119         // Load WebView
120         final MyWebView myWebView = mActivity.loadWebView(mUiBot);
121         mUiBot.waitForIdleSync();
122         // Initial check to make sure autofill is enabled in the application context
123         Helper.assertAutofillEnabled(myWebView.getContext(), true);
124 
125         // Set expectations.
126         myWebView.expectAutofill("dude", "sweet");
127         sReplier.addResponse(new CannedDataset.Builder()
128                 .setField(HTML_NAME_USERNAME, "dude")
129                 .setField(HTML_NAME_PASSWORD, "sweet")
130                 .setPresentation(createPresentation("The Dude"))
131                 .setInlinePresentation(createInlinePresentation("The Dude"))
132                 .build());
133 
134         // Trigger autofill.
135         mActivity.getUsernameInput().click();
136         mUiBot.waitForIdleSync();
137         final FillRequest fillRequest = sReplier.getNextFillRequest();
138         mUiBot.assertDatasets("The Dude");
139 
140         // Change focus around
141         mActivity.getUsernameLabel().click();
142         mUiBot.waitForIdleSync();
143         mUiBot.assertNoDatasets();
144         mActivity.getPasswordInput().click();
145         mUiBot.waitForIdleSync();
146         final UiObject2 datasetPicker = mUiBot.assertDatasets("The Dude");
147 
148         // Now Autofill it.
149         mUiBot.selectDataset(datasetPicker, "The Dude");
150         myWebView.assertAutofilled();
151         mUiBot.assertNoDatasets();
152 
153         try {
154             final ViewNode webViewNode =
155                     Helper.findWebViewNodeByFormName(fillRequest.structure, "FORM AM I");
156             assertThat(webViewNode.getClassName()).isEqualTo("android.webkit.WebView");
157             assertThat(webViewNode.getWebDomain()).isEqualTo(WebViewActivity.FAKE_DOMAIN);
158             assertThat(webViewNode.getWebScheme()).isEqualTo("https");
159 
160             final ViewNode usernameNode =
161                     Helper.findNodeByHtmlName(fillRequest.structure, HTML_NAME_USERNAME);
162             Helper.assertTextIsSanitized(usernameNode);
163             final HtmlInfo usernameHtmlInfo = Helper.assertHasHtmlTag(usernameNode, "input");
164             Helper.assertHasAttribute(usernameHtmlInfo, "type", "text");
165             Helper.assertHasAttribute(usernameHtmlInfo, "name", "username");
166             assertThat(usernameNode.isFocused()).isTrue();
167             assertThat(usernameNode.getAutofillHints()).asList().containsExactly("username");
168             assertThat(usernameNode.getHint()).isEqualTo("There's no place like a holder");
169 
170             final ViewNode passwordNode =
171                     Helper.findNodeByHtmlName(fillRequest.structure, HTML_NAME_PASSWORD);
172             Helper.assertTextIsSanitized(passwordNode);
173             final HtmlInfo passwordHtmlInfo = Helper.assertHasHtmlTag(passwordNode, "input");
174             Helper.assertHasAttribute(passwordHtmlInfo, "type", "password");
175             Helper.assertHasAttribute(passwordHtmlInfo, "name", "password");
176             assertThat(passwordNode.getAutofillHints()).asList()
177                     .containsExactly("current-password");
178             assertThat(passwordNode.getHint()).isEqualTo("Holder it like it cannnot passer a word");
179             assertThat(passwordNode.isFocused()).isFalse();
180         } catch (RuntimeException | Error e) {
181             Helper.dumpStructure("failed on testAutofillOneDataset()", fillRequest.structure);
182             throw e;
183         }
184     }
185 
186     @Test
187     public void testAutofillAndSave() throws Exception {
188         // Set service.
189         enableService();
190 
191         // Load WebView
192         final MyWebView myWebView = mActivity.loadWebView(mUiBot);
193 
194         // Set expectations.
195         myWebView.expectAutofill("dude", "sweet");
196         sReplier.addResponse(new CannedFillResponse.Builder()
197                 .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD,
198                         HTML_NAME_USERNAME, HTML_NAME_PASSWORD)
199                 .addDataset(new CannedDataset.Builder()
200                         .setField(HTML_NAME_USERNAME, "dude")
201                         .setField(HTML_NAME_PASSWORD, "sweet")
202                         .setPresentation(createPresentation("The Dude"))
203                         .setInlinePresentation(createInlinePresentation("The Dude"))
204                         .build())
205                 .build());
206 
207 
208         // Trigger autofill.
209         mActivity.getUsernameInput().click();
210         final FillRequest fillRequest = sReplier.getNextFillRequest();
211         mUiBot.assertDatasets("The Dude");
212 
213         // Assert structure passed to service.
214         final ViewNode usernameNode = Helper.findNodeByHtmlName(fillRequest.structure,
215                 HTML_NAME_USERNAME);
216         Helper.assertTextIsSanitized(usernameNode);
217         assertThat(usernameNode.isFocused()).isTrue();
218         assertThat(usernameNode.getAutofillHints()).asList().containsExactly("username");
219         final ViewNode passwordNode = Helper.findNodeByHtmlName(fillRequest.structure,
220                 HTML_NAME_PASSWORD);
221         Helper.assertTextIsSanitized(passwordNode);
222         assertThat(passwordNode.getAutofillHints()).asList().containsExactly("current-password");
223         assertThat(passwordNode.isFocused()).isFalse();
224 
225         // Autofill it.
226         mUiBot.selectDataset("The Dude");
227         myWebView.assertAutofilled();
228 
229         // Now trigger save.
230         mActivity.getUsernameInput().click();
231         mActivity.dispatchKeyPress(KeyEvent.KEYCODE_U);
232         mActivity.getPasswordInput().click();
233         mActivity.dispatchKeyPress(KeyEvent.KEYCODE_P);
234         mActivity.getLoginButton().click();
235 
236         // Assert save UI shown.
237         mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
238 
239         // Assert results
240         final SaveRequest saveRequest = sReplier.getNextSaveRequest();
241         final ViewNode usernameNode2 = Helper.findNodeByHtmlName(saveRequest.structure,
242                 HTML_NAME_USERNAME);
243         final ViewNode passwordNode2 = Helper.findNodeByHtmlName(saveRequest.structure,
244                 HTML_NAME_PASSWORD);
245 
246         Helper.assertTextAndValue(usernameNode2, "dudeu");
247         Helper.assertTextAndValue(passwordNode2, "sweetp");
248     }
249 }
250