<lambda>null1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
2 * Any copyright is dedicated to the Public Domain.
3 http://creativecommons.org/publicdomain/zero/1.0/ */
4
5 package org.mozilla.geckoview.test
6
7 import androidx.test.filters.MediumTest
8 import androidx.test.ext.junit.runners.AndroidJUnit4
9
10 import android.os.Handler
11 import android.os.Looper
12 import android.view.KeyEvent
13
14 import org.hamcrest.Matchers.*
15
16 import org.junit.Test
17 import org.junit.runner.RunWith
18
19 import org.mozilla.geckoview.GeckoResult
20 import org.mozilla.geckoview.GeckoSession
21 import org.mozilla.geckoview.GeckoSession.PromptDelegate
22 import org.mozilla.geckoview.GeckoSession.PromptDelegate.AutocompleteRequest
23 import org.mozilla.geckoview.Autocomplete.Address
24 import org.mozilla.geckoview.Autocomplete.AddressSelectOption
25 import org.mozilla.geckoview.Autocomplete.CreditCard
26 import org.mozilla.geckoview.Autocomplete.CreditCardSelectOption
27 import org.mozilla.geckoview.Autocomplete.LoginEntry
28 import org.mozilla.geckoview.Autocomplete.LoginSaveOption
29 import org.mozilla.geckoview.Autocomplete.LoginSelectOption
30 import org.mozilla.geckoview.Autocomplete.SelectOption
31 import org.mozilla.geckoview.Autocomplete.StorageDelegate
32 import org.mozilla.geckoview.Autocomplete.UsedField
33 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule
34 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.AssertCalled
35 import org.mozilla.geckoview.test.util.Callbacks
36
37
38 @RunWith(AndroidJUnit4::class)
39 @MediumTest
40 class AutocompleteTest : BaseSessionTest() {
41 val acceptDelay: Long = 100
42
43 @Test
44 fun fetchLogins() {
45 sessionRule.setPrefsUntilTestEnd(mapOf(
46 // Enable login management since it's disabled in automation.
47 "signon.rememberSignons" to true,
48 "signon.autofillForms.http" to true))
49
50 val runtime = sessionRule.runtime
51 val register = { delegate: StorageDelegate ->
52 runtime.autocompleteStorageDelegate = delegate
53 }
54 val unregister = { _: StorageDelegate ->
55 runtime.autocompleteStorageDelegate = null
56 }
57
58 val fetchHandled = GeckoResult<Void>()
59
60 sessionRule.addExternalDelegateDuringNextWait(
61 StorageDelegate::class, register, unregister,
62 object : StorageDelegate {
63 @AssertCalled(count = 1)
64 override fun onLoginFetch(domain: String)
65 : GeckoResult<Array<LoginEntry>>? {
66 assertThat("Domain should match", domain, equalTo("localhost"))
67
68 Handler(Looper.getMainLooper()).postDelayed({
69 fetchHandled.complete(null)
70 }, acceptDelay)
71
72 return null
73 }
74 })
75
76 mainSession.loadTestPath(FORMS3_HTML_PATH)
77 sessionRule.waitForResult(fetchHandled)
78 }
79
80 @Test
81 fun fetchCreditCards() {
82 val runtime = sessionRule.runtime
83 val register = { delegate: StorageDelegate ->
84 runtime.autocompleteStorageDelegate = delegate
85 }
86 val unregister = { _: StorageDelegate ->
87 runtime.autocompleteStorageDelegate = null
88 }
89
90 val fetchHandled = GeckoResult<Void>()
91
92 mainSession.loadTestPath(CC_FORM_HTML_PATH)
93 mainSession.waitForPageStop()
94
95 sessionRule.addExternalDelegateDuringNextWait(
96 StorageDelegate::class, register, unregister,
97 object : StorageDelegate {
98 @AssertCalled(count = 1)
99 override fun onCreditCardFetch()
100 : GeckoResult<Array<CreditCard>>? {
101 Handler(Looper.getMainLooper()).postDelayed({
102 fetchHandled.complete(null)
103 }, acceptDelay)
104
105 return null
106 }
107 })
108
109 mainSession.evaluateJS("document.querySelector('#name').focus()")
110 sessionRule.waitForResult(fetchHandled)
111 }
112
113 @Test
114 fun creditCardSelectAndFill() {
115 // Test:
116 // 1. Load a credit card form page.
117 // 2. Focus on the name input field.
118 // a. Ensure onCreditCardFetch is called.
119 // b. Return the saved entries.
120 // c. Ensure onCreditCardSelect is called.
121 // d. Select and return one of the options.
122 // e. Ensure the form is filled accordingly.
123
124 val runtime = sessionRule.runtime
125 val register = { delegate: StorageDelegate ->
126 runtime.autocompleteStorageDelegate = delegate
127 }
128 val unregister = { _: StorageDelegate ->
129 runtime.autocompleteStorageDelegate = null
130 }
131
132 val name = arrayOf("Peter Parker", "John Doe")
133 val number = arrayOf("1234-1234-1234-1234", "2345-2345-2345-2345")
134 val guid = arrayOf("test-guid1", "test-guid2")
135 val expMonth = arrayOf("Apr", "Aug")
136 val expYear = arrayOf("22", "23")
137 val savedCC = arrayOf(
138 CreditCard.Builder()
139 .guid(guid[0])
140 .name(name[0])
141 .number(number[0])
142 .expirationMonth(expMonth[0])
143 .expirationYear(expYear[0])
144 .build(),
145 CreditCard.Builder()
146 .guid(guid[1])
147 .name(name[1])
148 .number(number[1])
149 .expirationMonth(expMonth[1])
150 .expirationYear(expYear[1])
151 .build())
152
153 val selectHandled = GeckoResult<Void>()
154
155 mainSession.loadTestPath(CC_FORM_HTML_PATH)
156 mainSession.waitForPageStop()
157
158 sessionRule.addExternalDelegateDuringNextWait(
159 StorageDelegate::class, register, unregister,
160 object : StorageDelegate {
161 @AssertCalled
162 override fun onCreditCardFetch()
163 : GeckoResult<Array<CreditCard>>? {
164 return GeckoResult.fromValue(savedCC)
165 }
166 })
167
168 mainSession.delegateUntilTestEnd(object : Callbacks.PromptDelegate {
169 @AssertCalled(count = 1)
170 override fun onCreditCardSelect(
171 session: GeckoSession,
172 prompt: AutocompleteRequest<CreditCardSelectOption>)
173 : GeckoResult<PromptDelegate.PromptResponse>? {
174 assertThat("Session should not be null", session, notNullValue())
175
176 assertThat(
177 "There should be two options",
178 prompt.options.size,
179 equalTo(2))
180
181 for (i in 0..1) {
182 val creditCard = prompt.options[i].value
183
184 assertThat("Credit card should not be null", creditCard, notNullValue())
185 assertThat(
186 "Name should match",
187 creditCard.name,
188 equalTo(name[i]))
189 assertThat(
190 "Number should match",
191 creditCard.number,
192 equalTo(number[i]))
193 }
194 Handler(Looper.getMainLooper()).postDelayed({
195 selectHandled.complete(null)
196 }, acceptDelay)
197
198 return GeckoResult.fromValue(prompt.confirm(prompt.options[0]))
199 }
200 })
201
202 // Focus on the name input field.
203 mainSession.evaluateJS("document.querySelector('#name').focus()")
204 sessionRule.waitForResult(selectHandled)
205
206 assertThat(
207 "Filled name should match",
208 mainSession.evaluateJS("document.querySelector('#name').value") as String,
209 equalTo(name[0]))
210 assertThat(
211 "Filled number should match",
212 mainSession.evaluateJS("document.querySelector('#number').value") as String,
213 equalTo(number[0]))
214 assertThat(
215 "Filled expiration month should match",
216 mainSession.evaluateJS("document.querySelector('#expMonth').value") as String,
217 equalTo(expMonth[0]))
218 assertThat(
219 "Filled expiration year should match",
220 mainSession.evaluateJS("document.querySelector('#expYear').value") as String,
221 equalTo(expYear[0]))
222 }
223
224 @Test
225 fun fetchAddresses() {
226 val runtime = sessionRule.runtime
227 val register = { delegate: StorageDelegate ->
228 runtime.autocompleteStorageDelegate = delegate
229 }
230 val unregister = { _: StorageDelegate ->
231 runtime.autocompleteStorageDelegate = null
232 }
233
234 val fetchHandled = GeckoResult<Void>()
235
236 sessionRule.addExternalDelegateUntilTestEnd(
237 StorageDelegate::class, register, unregister,
238 object : StorageDelegate {
239 @AssertCalled(count = 1)
240 override fun onAddressFetch()
241 : GeckoResult<Array<Address>>? {
242 Handler(Looper.getMainLooper()).postDelayed({
243 fetchHandled.complete(null)
244 }, acceptDelay)
245
246 return null
247 }
248 })
249
250 mainSession.loadTestPath(ADDRESS_FORM_HTML_PATH)
251 mainSession.waitForPageStop()
252 mainSession.evaluateJS("document.querySelector('#name').focus()")
253 sessionRule.waitForResult(fetchHandled)
254 }
255
256 fun checkAddressesForCorrectness(savedAddresses: Array<Address>, selectedAddress: Address) {
257 // Test:
258 // 1. Load an address form page.
259 // 2. Focus on the given name input field.
260 // a. Ensure onAddressFetch is called.
261 // b. Return the saved entries.
262 // c. Ensure onAddressSelect is called.
263 // d. Select and return one of the options.
264 // e. Ensure the form is filled accordingly.
265
266 val runtime = sessionRule.runtime
267 val register = { delegate: StorageDelegate ->
268 runtime.autocompleteStorageDelegate = delegate
269 }
270 val unregister = { _: StorageDelegate ->
271 runtime.autocompleteStorageDelegate = null
272 }
273
274 val selectHandled = GeckoResult<Void>()
275
276 sessionRule.addExternalDelegateUntilTestEnd(
277 StorageDelegate::class, register, unregister,
278 object : StorageDelegate {
279 @AssertCalled
280 override fun onAddressFetch()
281 : GeckoResult<Array<Address>>? {
282 return GeckoResult.fromValue(savedAddresses)
283 }
284
285 @AssertCalled(false)
286 override fun onAddressSave(address: Address) {}
287 })
288
289 mainSession.delegateUntilTestEnd(object : Callbacks.PromptDelegate {
290 @AssertCalled(count = 1)
291 override fun onAddressSelect(
292 session: GeckoSession,
293 prompt: AutocompleteRequest<AddressSelectOption>)
294 : GeckoResult<PromptDelegate.PromptResponse>? {
295 assertThat("Session should not be null", session, notNullValue())
296
297 assertThat(
298 "There should be one option",
299 prompt.options.size,
300 equalTo(savedAddresses.size))
301
302 val addressOption = prompt.options.find { it.value.familyName == selectedAddress.familyName }
303 val address = addressOption?.value
304
305 assertThat("Address should not be null", address, notNullValue())
306 assertThat(
307 "Given name should match",
308 address?.givenName,
309 equalTo(selectedAddress.givenName))
310 assertThat(
311 "Family name should match",
312 address?.familyName,
313 equalTo(selectedAddress.familyName))
314 assertThat(
315 "Street address should match",
316 address?.streetAddress,
317 equalTo(selectedAddress.streetAddress))
318
319 Handler(Looper.getMainLooper()).postDelayed({
320 selectHandled.complete(null)
321 }, acceptDelay)
322
323 return GeckoResult.fromValue(prompt.confirm(addressOption!!))
324 }
325 })
326
327 mainSession.loadTestPath(ADDRESS_FORM_HTML_PATH)
328 mainSession.waitForPageStop()
329
330 // Focus on the given name input field.
331 mainSession.evaluateJS("document.querySelector('#givenName').focus()")
332 sessionRule.waitForResult(selectHandled)
333
334 assertThat(
335 "Filled given name should match",
336 mainSession.evaluateJS("document.querySelector('#givenName').value") as String,
337 equalTo(selectedAddress.givenName))
338 assertThat(
339 "Filled family name should match",
340 mainSession.evaluateJS("document.querySelector('#familyName').value") as String,
341 equalTo(selectedAddress.familyName))
342 assertThat(
343 "Filled street address should match",
344 mainSession.evaluateJS("document.querySelector('#streetAddress').value") as String,
345 equalTo(selectedAddress.streetAddress))
346 assertThat(
347 "Filled country should match",
348 mainSession.evaluateJS("document.querySelector('#country').value") as String,
349 equalTo(selectedAddress.country))
350 assertThat(
351 "Filled postal code should match",
352 mainSession.evaluateJS("document.querySelector('#postalCode').value") as String,
353 equalTo(selectedAddress.postalCode))
354 assertThat(
355 "Filled email should match",
356 mainSession.evaluateJS("document.querySelector('#email').value") as String,
357 equalTo(selectedAddress.email))
358 assertThat(
359 "Filled telephone number should match",
360 mainSession.evaluateJS("document.querySelector('#tel').value") as String,
361 equalTo(selectedAddress.tel))
362 assertThat(
363 "Filled organization should match",
364 mainSession.evaluateJS("document.querySelector('#organization').value") as String,
365 equalTo(selectedAddress.organization))
366 }
367
368 @Test
369 fun addressSelectAndFill() {
370 val givenName = "Peter"
371 val familyName = "Parker"
372 val streetAddress = "20 Ingram Street, Forest Hills Gardens, Queens"
373 val postalCode = "11375"
374 val country = "US"
375 val email = "spiderman@newyork.com"
376 val tel = "+1 180090021"
377 val organization = ""
378 val guid = "test-guid"
379 val savedAddress = Address.Builder()
380 .guid(guid)
381 .givenName(givenName)
382 .familyName(familyName)
383 .streetAddress(streetAddress)
384 .postalCode(postalCode)
385 .country(country)
386 .email(email)
387 .tel(tel)
388 .organization(organization)
389 .build()
390 val savedAddresses = mutableListOf<Address>(savedAddress)
391
392 checkAddressesForCorrectness(savedAddresses.toTypedArray(), savedAddress)
393 }
394
395
396 @Test
397 fun addressSelectAndFillMultipleAddresses() {
398 val givenNames = arrayOf("Peter", "Wade")
399 val familyNames = arrayOf("Parker", "Wilson")
400 val streetAddresses = arrayOf("20 Ingram Street, Forest Hills Gardens, Queens", "890 Fifth Avenue, Manhattan")
401 val postalCodes = arrayOf("11375", "10110")
402 val countries = arrayOf("US", "US")
403 val emails = arrayOf("spiderman@newyork.com", "deadpool@newyork.com")
404 val tels = arrayOf("+1 180090021", "+1 180055555")
405 val organizations = arrayOf("", "")
406 val guids = arrayOf("test-guid-1", "test-guid-2")
407 val selectedAddress = Address.Builder()
408 .guid(guids[1])
409 .givenName(givenNames[1])
410 .familyName(familyNames[1])
411 .streetAddress(streetAddresses[1])
412 .postalCode(postalCodes[1])
413 .country(countries[1])
414 .email(emails[1])
415 .tel(tels[1])
416 .organization(organizations[1])
417 .build()
418 val savedAddresses = mutableListOf<Address>(
419 Address.Builder()
420 .guid(guids[0])
421 .givenName(givenNames[0])
422 .familyName(familyNames[0])
423 .streetAddress(streetAddresses[0])
424 .postalCode(postalCodes[0])
425 .country(countries[0])
426 .email(emails[0])
427 .tel(tels[0])
428 .organization(organizations[0])
429 .build(),
430 selectedAddress
431 )
432
433 checkAddressesForCorrectness(savedAddresses.toTypedArray(), selectedAddress)
434 }
435
436 @Test
437 fun loginSaveDismiss() {
438 sessionRule.setPrefsUntilTestEnd(mapOf(
439 // Enable login management since it's disabled in automation.
440 "signon.rememberSignons" to true,
441 "signon.autofillForms.http" to true,
442 "signon.userInputRequiredToCapture.enabled" to false))
443
444 val runtime = sessionRule.runtime
445 val register = { delegate: StorageDelegate ->
446 runtime.autocompleteStorageDelegate = delegate
447 }
448 val unregister = { _: StorageDelegate ->
449 runtime.autocompleteStorageDelegate = null
450 }
451
452 sessionRule.addExternalDelegateDuringNextWait(
453 StorageDelegate::class, register, unregister,
454 object : StorageDelegate {
455 @AssertCalled(count = 1)
456 override fun onLoginFetch(domain: String)
457 : GeckoResult<Array<LoginEntry>>? {
458 assertThat("Domain should match", domain, equalTo("localhost"))
459
460 return null
461 }
462 })
463
464 mainSession.loadTestPath(FORMS3_HTML_PATH)
465 mainSession.waitForPageStop()
466
467 sessionRule.addExternalDelegateUntilTestEnd(
468 StorageDelegate::class, register, unregister,
469 object : StorageDelegate {
470 @AssertCalled(count = 0)
471 override fun onLoginSave(login: LoginEntry) {}
472 })
473
474 // Assign login credentials.
475 mainSession.evaluateJS("document.querySelector('#user1').value = 'user1x'")
476 mainSession.evaluateJS("document.querySelector('#pass1').value = 'pass1x'")
477
478 // Submit the form.
479 mainSession.evaluateJS("document.querySelector('#form1').submit()")
480
481 sessionRule.waitUntilCalled(object : Callbacks.PromptDelegate {
482 @AssertCalled(count = 1)
483 override fun onLoginSave(
484 session: GeckoSession,
485 prompt: AutocompleteRequest<LoginSaveOption>)
486 : GeckoResult<PromptDelegate.PromptResponse>? {
487 val option = prompt.options[0]
488 val login = option.value
489
490 assertThat("Session should not be null", session, notNullValue())
491 assertThat("Login should not be null", login, notNullValue())
492 assertThat(
493 "Username should match",
494 login.username,
495 equalTo("user1x"))
496
497 assertThat(
498 "Password should match",
499 login.password,
500 equalTo("pass1x"))
501
502 return GeckoResult.fromValue(prompt.dismiss())
503 }
504 })
505 }
506
507 @Test
508 fun loginSaveAccept() {
509 sessionRule.setPrefsUntilTestEnd(mapOf(
510 // Enable login management since it's disabled in automation.
511 "signon.rememberSignons" to true,
512 "signon.autofillForms.http" to true,
513 "signon.userInputRequiredToCapture.enabled" to false))
514
515 val runtime = sessionRule.runtime
516 val register = { delegate: StorageDelegate ->
517 runtime.autocompleteStorageDelegate = delegate
518 }
519 val unregister = { _: StorageDelegate ->
520 runtime.autocompleteStorageDelegate = null
521 }
522
523 mainSession.loadTestPath(FORMS3_HTML_PATH)
524 mainSession.waitForPageStop()
525
526 val saveHandled = GeckoResult<Void>()
527
528 sessionRule.addExternalDelegateUntilTestEnd(
529 StorageDelegate::class, register, unregister,
530 object : StorageDelegate {
531 @AssertCalled
532 override fun onLoginSave(login: LoginEntry) {
533 assertThat(
534 "Username should match",
535 login.username,
536 equalTo("user1x"))
537
538 assertThat(
539 "Password should match",
540 login.password,
541 equalTo("pass1x"))
542
543 saveHandled.complete(null)
544 }
545 })
546
547 sessionRule.delegateDuringNextWait(object : Callbacks.PromptDelegate {
548 @AssertCalled(count = 1)
549 override fun onLoginSave(
550 session: GeckoSession,
551 prompt: AutocompleteRequest<LoginSaveOption>)
552 : GeckoResult<PromptDelegate.PromptResponse>? {
553 assertThat("Session should not be null", session, notNullValue())
554
555 val option = prompt.options[0]
556 val login = option.value
557
558 assertThat("Login should not be null", login, notNullValue())
559
560 assertThat(
561 "Username should match",
562 login.username,
563 equalTo("user1x"))
564
565 assertThat(
566 "Password should match",
567 login.password,
568 equalTo("pass1x"))
569
570 return GeckoResult.fromValue(prompt.confirm(option))
571 }
572 })
573
574 // Assign login credentials.
575 mainSession.evaluateJS("document.querySelector('#user1').value = 'user1x'")
576 mainSession.evaluateJS("document.querySelector('#pass1').value = 'pass1x'")
577
578 // Submit the form.
579 mainSession.evaluateJS("document.querySelector('#form1').submit()")
580
581 sessionRule.waitForResult(saveHandled)
582 }
583
584 @Test
585 fun loginSaveModifyAccept() {
586 sessionRule.setPrefsUntilTestEnd(mapOf(
587 // Enable login management since it's disabled in automation.
588 "signon.rememberSignons" to true,
589 "signon.autofillForms.http" to true,
590 "signon.userInputRequiredToCapture.enabled" to false))
591
592 val runtime = sessionRule.runtime
593 val register = { delegate: StorageDelegate ->
594 runtime.autocompleteStorageDelegate = delegate
595 }
596 val unregister = { _: StorageDelegate ->
597 runtime.autocompleteStorageDelegate = null
598 }
599
600 mainSession.loadTestPath(FORMS3_HTML_PATH)
601 mainSession.waitForPageStop()
602
603 val saveHandled = GeckoResult<Void>()
604
605 sessionRule.addExternalDelegateUntilTestEnd(
606 StorageDelegate::class, register, unregister,
607 object : StorageDelegate {
608 @AssertCalled
609 override fun onLoginSave(login: LoginEntry) {
610 assertThat(
611 "Username should match",
612 login.username,
613 equalTo("user1x"))
614
615 assertThat(
616 "Password should match",
617 login.password,
618 equalTo("pass1xmod"))
619
620 saveHandled.complete(null)
621 }
622 })
623
624 sessionRule.delegateDuringNextWait(object : Callbacks.PromptDelegate {
625 @AssertCalled(count = 1)
626 override fun onLoginSave(
627 session: GeckoSession,
628 prompt: AutocompleteRequest<LoginSaveOption>)
629 : GeckoResult<PromptDelegate.PromptResponse>? {
630 assertThat("Session should not be null", session, notNullValue())
631
632 val option = prompt.options[0]
633 val login = option.value
634
635 assertThat("Login should not be null", login, notNullValue())
636
637 assertThat(
638 "Username should match",
639 login.username,
640 equalTo("user1x"))
641
642 assertThat(
643 "Password should match",
644 login.password,
645 equalTo("pass1x"))
646
647 val modLogin = LoginEntry.Builder()
648 .origin(login.origin)
649 .formActionOrigin(login.origin)
650 .httpRealm(login.httpRealm)
651 .username(login.username)
652 .password("pass1xmod")
653 .build()
654
655 return GeckoResult.fromValue(prompt.confirm(LoginSaveOption(modLogin)))
656 }
657 })
658
659 // Assign login credentials.
660 mainSession.evaluateJS("document.querySelector('#user1').value = 'user1x'")
661 mainSession.evaluateJS("document.querySelector('#pass1').value = 'pass1x'")
662
663 // Submit the form.
664 mainSession.evaluateJS("document.querySelector('#form1').submit()")
665
666 sessionRule.waitForResult(saveHandled)
667 }
668
669 @Test
670 fun loginUpdateAccept() {
671 sessionRule.setPrefsUntilTestEnd(mapOf(
672 // Enable login management since it's disabled in automation.
673 "signon.rememberSignons" to true,
674 "signon.autofillForms.http" to true,
675 "signon.userInputRequiredToCapture.enabled" to false))
676
677 val runtime = sessionRule.runtime
678 val register = { delegate: StorageDelegate ->
679 runtime.autocompleteStorageDelegate = delegate
680 }
681 val unregister = { _: StorageDelegate ->
682 runtime.autocompleteStorageDelegate = null
683 }
684
685 val saveHandled = GeckoResult<Void>()
686 val saveHandled2 = GeckoResult<Void>()
687
688 val user1 = "user1x"
689 val pass1 = "pass1x"
690 val pass2 = "pass1up"
691 val guid = "test-guid"
692 val savedLogins = mutableListOf<LoginEntry>()
693
694 sessionRule.addExternalDelegateUntilTestEnd(
695 StorageDelegate::class, register, unregister,
696 object : StorageDelegate {
697 @AssertCalled
698 override fun onLoginFetch(domain: String)
699 : GeckoResult<Array<LoginEntry>>? {
700 assertThat("Domain should match", domain, equalTo("localhost"))
701
702 return GeckoResult.fromValue(savedLogins.toTypedArray())
703 }
704
705 @AssertCalled(count = 2)
706 override fun onLoginSave(login: LoginEntry) {
707 assertThat(
708 "Username should match",
709 login.username,
710 equalTo(user1))
711
712 assertThat(
713 "Password should match",
714 login.password,
715 equalTo(forEachCall(pass1, pass2)))
716
717 assertThat(
718 "GUID should match",
719 login.guid,
720 equalTo(forEachCall(null, guid)))
721
722 val savedLogin = LoginEntry.Builder()
723 .guid(guid)
724 .origin(login.origin)
725 .formActionOrigin(login.formActionOrigin)
726 .username(login.username)
727 .password(login.password)
728 .build()
729
730 savedLogins.add(savedLogin)
731
732 if (sessionRule.currentCall.counter == 1) {
733 saveHandled.complete(null)
734 } else if (sessionRule.currentCall.counter == 2) {
735 saveHandled2.complete(null)
736 }
737 }
738 })
739
740 sessionRule.delegateUntilTestEnd(object : Callbacks.PromptDelegate {
741 @AssertCalled(count = 2)
742 override fun onLoginSave(
743 session: GeckoSession,
744 prompt: AutocompleteRequest<LoginSaveOption>)
745 : GeckoResult<PromptDelegate.PromptResponse>? {
746 assertThat("Session should not be null", session, notNullValue())
747
748 val option = prompt.options[0]
749 val login = option.value
750
751 assertThat("Login should not be null", login, notNullValue())
752
753 assertThat(
754 "Username should match",
755 login.username,
756 equalTo(user1))
757
758 assertThat(
759 "Password should match",
760 login.password,
761 equalTo(forEachCall(pass1, pass2)))
762
763 return GeckoResult.fromValue(prompt.confirm(option))
764 }
765 })
766
767 // Assign login credentials.
768 mainSession.loadTestPath(FORMS3_HTML_PATH)
769 mainSession.waitForPageStop()
770 mainSession.evaluateJS("document.querySelector('#user1').value = '$user1'")
771 mainSession.evaluateJS("document.querySelector('#pass1').value = '$pass1'")
772 mainSession.evaluateJS("document.querySelector('#form1').submit()")
773
774 sessionRule.waitForResult(saveHandled)
775
776 // Update login credentials.
777 val session2 = sessionRule.createOpenSession()
778 session2.loadTestPath(FORMS3_HTML_PATH)
779 session2.waitForPageStop()
780 session2.evaluateJS("document.querySelector('#pass1').value = '$pass2'")
781 session2.evaluateJS("document.querySelector('#form1').submit()")
782
783 sessionRule.waitForResult(saveHandled2)
784 }
785
786 fun testLoginUsed(autofillEnabled: Boolean) {
787 sessionRule.setPrefsUntilTestEnd(mapOf(
788 // Enable login management since it's disabled in automation.
789 "signon.rememberSignons" to true,
790 "signon.autofillForms.http" to true,
791 "signon.userInputRequiredToCapture.enabled" to false))
792
793 val runtime = sessionRule.runtime
794 val register = { delegate: StorageDelegate ->
795 runtime.autocompleteStorageDelegate = delegate
796 }
797 val unregister = { _: StorageDelegate ->
798 runtime.autocompleteStorageDelegate = null
799 }
800
801 val usedHandled = GeckoResult<Void>()
802
803 val user1 = "user1x"
804 val pass1 = "pass1x"
805 val guid = "test-guid"
806 val origin = GeckoSessionTestRule.TEST_ENDPOINT
807 val savedLogin = LoginEntry.Builder()
808 .guid(guid)
809 .origin(origin)
810 .formActionOrigin(origin)
811 .username(user1)
812 .password(pass1)
813 .build()
814 val savedLogins = mutableListOf<LoginEntry>(savedLogin)
815
816 if (autofillEnabled) {
817 sessionRule.addExternalDelegateUntilTestEnd(
818 StorageDelegate::class, register, unregister,
819 object : StorageDelegate {
820 @AssertCalled
821 override fun onLoginFetch(domain: String)
822 : GeckoResult<Array<LoginEntry>>? {
823 assertThat("Domain should match", domain, equalTo("localhost"))
824
825 return GeckoResult.fromValue(savedLogins.toTypedArray())
826 }
827
828 @AssertCalled(count = 1)
829 override fun onLoginUsed(login: LoginEntry, usedFields: Int) {
830 assertThat(
831 "Used fields should match",
832 usedFields,
833 equalTo(UsedField.PASSWORD))
834
835 assertThat(
836 "Username should match",
837 login.username,
838 equalTo(user1))
839
840 assertThat(
841 "Password should match",
842 login.password,
843 equalTo(pass1))
844
845 assertThat(
846 "GUID should match",
847 login.guid,
848 equalTo(guid))
849
850 usedHandled.complete(null)
851 }
852 })
853 } else {
854 sessionRule.addExternalDelegateUntilTestEnd(
855 StorageDelegate::class, register, unregister,
856 object : StorageDelegate {
857 @AssertCalled
858 override fun onLoginFetch(domain: String)
859 : GeckoResult<Array<LoginEntry>>? {
860 assertThat("Domain should match", domain, equalTo("localhost"))
861
862 return GeckoResult.fromValue(savedLogins.toTypedArray())
863 }
864
865 @AssertCalled(false)
866 override fun onLoginUsed(login: LoginEntry, usedFields: Int) {}
867 })
868 }
869
870 sessionRule.delegateUntilTestEnd(object : Callbacks.PromptDelegate {
871 @AssertCalled(false)
872 override fun onLoginSave(
873 session: GeckoSession,
874 prompt: AutocompleteRequest<LoginSaveOption>)
875 : GeckoResult<PromptDelegate.PromptResponse>? {
876 return null
877 }
878 })
879
880 mainSession.loadTestPath(FORMS3_HTML_PATH)
881 mainSession.waitForPageStop()
882 mainSession.evaluateJS("document.querySelector('#form1').submit()")
883
884 if (autofillEnabled) {
885 sessionRule.waitForResult(usedHandled)
886 } else {
887 mainSession.waitForPageStop()
888 }
889 }
890
891 @Test
892 fun loginUsed() {
893 testLoginUsed(true)
894 }
895
896 @Test
897 fun loginAutofillDisabled() {
898 sessionRule.runtime.settings.loginAutofillEnabled = false
899 testLoginUsed(false)
900 sessionRule.runtime.settings.loginAutofillEnabled = true
901 }
902
903 fun testPasswordAutofill(autofillEnabled: Boolean) {
904 sessionRule.setPrefsUntilTestEnd(mapOf(
905 // Enable login management since it's disabled in automation.
906 "signon.rememberSignons" to true,
907 "signon.autofillForms.http" to true,
908 "signon.userInputRequiredToCapture.enabled" to false))
909
910 val runtime = sessionRule.runtime
911 val register = { delegate: StorageDelegate ->
912 runtime.autocompleteStorageDelegate = delegate
913 }
914 val unregister = { _: StorageDelegate ->
915 runtime.autocompleteStorageDelegate = null
916 }
917
918 val user1 = "user1x"
919 val pass1 = "pass1x"
920 val guid = "test-guid"
921 val origin = GeckoSessionTestRule.TEST_ENDPOINT
922 val savedLogin = LoginEntry.Builder()
923 .guid(guid)
924 .origin(origin)
925 .formActionOrigin(origin)
926 .username(user1)
927 .password(pass1)
928 .build()
929 val savedLogins = mutableListOf<LoginEntry>(savedLogin)
930
931 sessionRule.addExternalDelegateUntilTestEnd(
932 StorageDelegate::class, register, unregister,
933 object : StorageDelegate {
934 @AssertCalled
935 override fun onLoginFetch(domain: String)
936 : GeckoResult<Array<LoginEntry>>? {
937 assertThat("Domain should match", domain, equalTo("localhost"))
938
939 return GeckoResult.fromValue(savedLogins.toTypedArray())
940 }
941
942 @AssertCalled(false)
943 override fun onLoginUsed(login: LoginEntry, usedFields: Int) {}
944 })
945
946 sessionRule.delegateUntilTestEnd(object : Callbacks.PromptDelegate {
947 @AssertCalled(false)
948 override fun onLoginSave(
949 session: GeckoSession,
950 prompt: AutocompleteRequest<LoginSaveOption>)
951 : GeckoResult<PromptDelegate.PromptResponse>? {
952 return null
953 }
954 })
955
956 mainSession.loadTestPath(FORMS3_HTML_PATH)
957 mainSession.waitForPageStop()
958 mainSession.evaluateJS("document.querySelector('#user1').focus()")
959 mainSession.evaluateJS(
960 "document.querySelector('#user1').value = '$user1'")
961 mainSession.pressKey(KeyEvent.KEYCODE_TAB)
962
963 val pass = mainSession.evaluateJS(
964 "document.querySelector('#pass1').value") as String
965
966 if (autofillEnabled) {
967 assertThat(
968 "Password should match",
969 pass,
970 equalTo(pass1))
971 } else {
972 assertThat(
973 "Password should not be filled",
974 pass,
975 equalTo(""))
976 }
977 }
978
979 @Test
980 fun loginAutofillDisabledPasswordAutofill() {
981 sessionRule.runtime.settings.loginAutofillEnabled = false
982 testPasswordAutofill(false)
983 sessionRule.runtime.settings.loginAutofillEnabled = true
984 }
985
986 @Test
987 fun loginAutofillEnabledPasswordAutofill() {
988 testPasswordAutofill(true)
989 }
990
991 @Test
992 fun loginSelectAccept() {
993 sessionRule.setPrefsUntilTestEnd(mapOf(
994 // Enable login management since it's disabled in automation.
995 "signon.rememberSignons" to true,
996 "signon.autofillForms.http" to true,
997 "dom.disable_open_during_load" to false,
998 "signon.userInputRequiredToCapture.enabled" to false))
999
1000 // Test:
1001 // 1. Load a login form page.
1002 // 2. Input un/pw and submit.
1003 // a. Ensure onLoginSave is called accordingly.
1004 // b. Save the submitted login entry.
1005 // 3. Reload the login form page.
1006 // a. Ensure onLoginFetch is called.
1007 // b. Return empty login entry list to avoid autofilling.
1008 // 4. Input a new set of un/pw and submit.
1009 // a. Ensure onLoginSave is called again.
1010 // b. Save the submitted login entry.
1011 // 5. Reload the login form page.
1012 // 6. Focus on the username input field.
1013 // a. Ensure onLoginFetch is called.
1014 // b. Return the saved login entries.
1015 // c. Ensure onLoginSelect is called.
1016 // d. Select and return one of the options.
1017 // e. Submit the form.
1018 // f. Ensure that onLoginUsed is called.
1019
1020 val runtime = sessionRule.runtime
1021 val register = { delegate: StorageDelegate ->
1022 runtime.autocompleteStorageDelegate = delegate
1023 }
1024 val unregister = { _: StorageDelegate ->
1025 runtime.autocompleteStorageDelegate = null
1026 }
1027
1028 val user1 = "user1x"
1029 val user2 = "user2x"
1030 val pass1 = "pass1x"
1031 val pass2 = "pass2x"
1032 val savedLogins = mutableListOf<LoginEntry>()
1033
1034 val saveHandled1 = GeckoResult<Void>()
1035 val saveHandled2 = GeckoResult<Void>()
1036 val selectHandled = GeckoResult<Void>()
1037 val usedHandled = GeckoResult<Void>()
1038
1039 sessionRule.addExternalDelegateUntilTestEnd(
1040 StorageDelegate::class, register, unregister,
1041 object : StorageDelegate {
1042 @AssertCalled
1043 override fun onLoginFetch(domain: String)
1044 : GeckoResult<Array<LoginEntry>>? {
1045 assertThat("Domain should match", domain, equalTo("localhost"))
1046
1047 var logins = mutableListOf<LoginEntry>()
1048
1049 if (savedLogins.size == 2) {
1050 logins = savedLogins
1051 }
1052
1053 return GeckoResult.fromValue(logins.toTypedArray())
1054 }
1055
1056 @AssertCalled(count = 2)
1057 override fun onLoginSave(login: LoginEntry) {
1058 var username = ""
1059 var password = ""
1060 var handle = GeckoResult<Void>()
1061
1062 if (sessionRule.currentCall.counter == 1) {
1063 username = user1
1064 password = pass1
1065 handle = saveHandled1
1066 } else if (sessionRule.currentCall.counter == 2) {
1067 username = user2
1068 password = pass2
1069 handle = saveHandled2
1070 }
1071
1072 val savedLogin = LoginEntry.Builder()
1073 .guid(login.username)
1074 .origin(login.origin)
1075 .formActionOrigin(login.formActionOrigin)
1076 .username(login.username)
1077 .password(login.password)
1078 .build()
1079
1080 savedLogins.add(savedLogin)
1081
1082 assertThat(
1083 "Username should match",
1084 login.username,
1085 equalTo(username))
1086
1087 assertThat(
1088 "Password should match",
1089 login.password,
1090 equalTo(password))
1091
1092 handle.complete(null)
1093 }
1094
1095 @AssertCalled(count = 1)
1096 override fun onLoginUsed(login: LoginEntry, usedFields: Int) {
1097 assertThat(
1098 "Used fields should match",
1099 usedFields,
1100 equalTo(UsedField.PASSWORD))
1101
1102 assertThat(
1103 "Username should match",
1104 login.username,
1105 equalTo(user1))
1106
1107 assertThat(
1108 "Password should match",
1109 login.password,
1110 equalTo(pass1))
1111
1112 assertThat(
1113 "GUID should match",
1114 login.guid,
1115 equalTo(user1))
1116
1117 usedHandled.complete(null)
1118 }
1119 })
1120
1121 mainSession.loadTestPath(FORMS3_HTML_PATH)
1122 mainSession.waitForPageStop()
1123
1124 mainSession.delegateDuringNextWait(object : Callbacks.PromptDelegate {
1125 @AssertCalled(count = 1)
1126 override fun onLoginSave(
1127 session: GeckoSession,
1128 prompt: AutocompleteRequest<LoginSaveOption>)
1129 : GeckoResult<PromptDelegate.PromptResponse>? {
1130 assertThat("Session should not be null", session, notNullValue())
1131
1132 val option = prompt.options[0]
1133 val login = option.value
1134
1135 assertThat("Login should not be null", login, notNullValue())
1136
1137 assertThat(
1138 "Username should match",
1139 login.username,
1140 equalTo(user1))
1141
1142 assertThat(
1143 "Password should match",
1144 login.password,
1145 equalTo(pass1))
1146
1147 return GeckoResult.fromValue(prompt.confirm(option))
1148 }
1149 })
1150
1151 // Assign login credentials.
1152 mainSession.evaluateJS("document.querySelector('#user1').value = '$user1'")
1153 mainSession.evaluateJS("document.querySelector('#pass1').value = '$pass1'")
1154
1155 // Submit the form.
1156 mainSession.evaluateJS("document.querySelector('#form1').submit()")
1157 sessionRule.waitForResult(saveHandled1)
1158
1159 // Reload.
1160 val session2 = sessionRule.createOpenSession()
1161 session2.loadTestPath(FORMS3_HTML_PATH)
1162 session2.waitForPageStop()
1163
1164 session2.delegateDuringNextWait(object : Callbacks.PromptDelegate {
1165 @AssertCalled(count = 1)
1166 override fun onLoginSave(
1167 session: GeckoSession,
1168 prompt: AutocompleteRequest<LoginSaveOption>)
1169 : GeckoResult<PromptDelegate.PromptResponse>? {
1170 assertThat("Session should not be null", session, notNullValue())
1171
1172 val option = prompt.options[0]
1173 val login = option.value
1174
1175 assertThat("Login should not be null", login, notNullValue())
1176
1177 assertThat(
1178 "Username should match",
1179 login.username,
1180 equalTo(user2))
1181
1182 assertThat(
1183 "Password should match",
1184 login.password,
1185 equalTo(pass2))
1186
1187 return GeckoResult.fromValue(prompt.confirm(option))
1188 }
1189 })
1190
1191 // Assign alternative login credentials.
1192 session2.evaluateJS("document.querySelector('#user1').value = '$user2'")
1193 session2.evaluateJS("document.querySelector('#pass1').value = '$pass2'")
1194
1195 // Submit the form.
1196 session2.evaluateJS("document.querySelector('#form1').submit()")
1197 sessionRule.waitForResult(saveHandled2)
1198
1199 // Reload for the last time.
1200 val session3 = sessionRule.createOpenSession()
1201
1202 session3.delegateUntilTestEnd(object : Callbacks.PromptDelegate {
1203 @AssertCalled(count = 1)
1204 override fun onLoginSelect(
1205 session: GeckoSession,
1206 prompt: AutocompleteRequest<LoginSelectOption>)
1207 : GeckoResult<PromptDelegate.PromptResponse>? {
1208 assertThat("Session should not be null", session, notNullValue())
1209
1210 assertThat(
1211 "There should be two options",
1212 prompt.options.size,
1213 equalTo(2))
1214
1215 var usernames = arrayOf(user1, user2)
1216 var passwords = arrayOf(pass1, pass2)
1217
1218 for (i in 0..1) {
1219 val login = prompt.options[i].value
1220
1221 assertThat("Login should not be null", login, notNullValue())
1222 assertThat(
1223 "Username should match",
1224 login.username,
1225 equalTo(usernames[i]))
1226 assertThat(
1227 "Password should match",
1228 login.password,
1229 equalTo(passwords[i]))
1230 }
1231
1232
1233 Handler(Looper.getMainLooper()).postDelayed({
1234 selectHandled.complete(null)
1235 }, acceptDelay)
1236
1237 return GeckoResult.fromValue(prompt.confirm(prompt.options[0]))
1238 }
1239 })
1240
1241 session3.loadTestPath(FORMS3_HTML_PATH)
1242 session3.waitForPageStop()
1243
1244 // Focus on the username input field.
1245 session3.evaluateJS("document.querySelector('#user1').focus()")
1246 sessionRule.waitForResult(selectHandled)
1247
1248 assertThat(
1249 "Filled username should match",
1250 session3.evaluateJS("document.querySelector('#user1').value") as String,
1251 equalTo(user1))
1252
1253 assertThat(
1254 "Filled password should match",
1255 session3.evaluateJS("document.querySelector('#pass1').value") as String,
1256 equalTo(pass1))
1257
1258 // Submit the selection.
1259 session3.evaluateJS("document.querySelector('#form1').submit()")
1260 sessionRule.waitForResult(usedHandled)
1261 }
1262
1263 @Test
1264 fun loginSelectModifyAccept() {
1265 sessionRule.setPrefsUntilTestEnd(mapOf(
1266 // Enable login management since it's disabled in automation.
1267 "signon.rememberSignons" to true,
1268 "signon.autofillForms.http" to true,
1269 "dom.disable_open_during_load" to false,
1270 "signon.userInputRequiredToCapture.enabled" to false))
1271
1272 // Test:
1273 // 1. Load a login form page.
1274 // 2. Input un/pw and submit.
1275 // a. Ensure onLoginSave is called accordingly.
1276 // b. Save the submitted login entry.
1277 // 3. Reload the login form page.
1278 // a. Ensure onLoginFetch is called.
1279 // b. Return empty login entry list to avoid autofilling.
1280 // 4. Input a new set of un/pw and submit.
1281 // a. Ensure onLoginSave is called again.
1282 // b. Save the submitted login entry.
1283 // 5. Reload the login form page.
1284 // 6. Focus on the username input field.
1285 // a. Ensure onLoginFetch is called.
1286 // b. Return the saved login entries.
1287 // c. Ensure onLoginSelect is called.
1288 // d. Select and return a new login entry.
1289 // e. Submit the form.
1290 // f. Ensure that onLoginUsed is not called.
1291
1292 val runtime = sessionRule.runtime
1293 val register = { delegate: StorageDelegate ->
1294 runtime.autocompleteStorageDelegate = delegate
1295 }
1296 val unregister = { _: StorageDelegate ->
1297 runtime.autocompleteStorageDelegate = null
1298 }
1299
1300 val user1 = "user1x"
1301 val user2 = "user2x"
1302 val pass1 = "pass1x"
1303 val pass2 = "pass2x"
1304 val userMod = "user1xmod"
1305 val passMod = "pass1xmod"
1306 val savedLogins = mutableListOf<LoginEntry>()
1307
1308 val saveHandled1 = GeckoResult<Void>()
1309 val saveHandled2 = GeckoResult<Void>()
1310 val selectHandled = GeckoResult<Void>()
1311
1312 sessionRule.addExternalDelegateUntilTestEnd(
1313 StorageDelegate::class, register, unregister,
1314 object : StorageDelegate {
1315 @AssertCalled
1316 override fun onLoginFetch(domain: String)
1317 : GeckoResult<Array<LoginEntry>>? {
1318 assertThat("Domain should match", domain, equalTo("localhost"))
1319
1320 var logins = mutableListOf<LoginEntry>()
1321
1322 if (savedLogins.size == 2) {
1323 logins = savedLogins
1324 }
1325
1326 return GeckoResult.fromValue(logins.toTypedArray())
1327 }
1328
1329 @AssertCalled(count = 2)
1330 override fun onLoginSave(login: LoginEntry) {
1331 var username = ""
1332 var password = ""
1333 var handle = GeckoResult<Void>()
1334
1335 if (sessionRule.currentCall.counter == 1) {
1336 username = user1
1337 password = pass1
1338 handle = saveHandled1
1339 } else if (sessionRule.currentCall.counter == 2) {
1340 username = user2
1341 password = pass2
1342 handle = saveHandled2
1343 }
1344
1345 val savedLogin = LoginEntry.Builder()
1346 .guid(login.username)
1347 .origin(login.origin)
1348 .formActionOrigin(login.formActionOrigin)
1349 .username(login.username)
1350 .password(login.password)
1351 .build()
1352
1353 savedLogins.add(savedLogin)
1354
1355 assertThat(
1356 "Username should match",
1357 login.username,
1358 equalTo(username))
1359
1360 assertThat(
1361 "Password should match",
1362 login.password,
1363 equalTo(password))
1364
1365 handle.complete(null)
1366 }
1367
1368 @AssertCalled(false)
1369 override fun onLoginUsed(login: LoginEntry, usedFields: Int) {}
1370 })
1371
1372 mainSession.loadTestPath(FORMS3_HTML_PATH)
1373 mainSession.waitForPageStop()
1374
1375 mainSession.delegateDuringNextWait(object : Callbacks.PromptDelegate {
1376 @AssertCalled(count = 1)
1377 override fun onLoginSave(
1378 session: GeckoSession,
1379 prompt: AutocompleteRequest<LoginSaveOption>)
1380 : GeckoResult<PromptDelegate.PromptResponse>? {
1381 assertThat("Session should not be null", session, notNullValue())
1382
1383 val option = prompt.options[0]
1384 val login = option.value
1385
1386 assertThat("Login should not be null", login, notNullValue())
1387
1388 assertThat(
1389 "Username should match",
1390 login.username,
1391 equalTo(user1))
1392
1393 assertThat(
1394 "Password should match",
1395 login.password,
1396 equalTo(pass1))
1397
1398 return GeckoResult.fromValue(prompt.confirm(option))
1399 }
1400 })
1401
1402 // Assign login credentials.
1403 mainSession.evaluateJS("document.querySelector('#user1').value = '$user1'")
1404 mainSession.evaluateJS("document.querySelector('#pass1').value = '$pass1'")
1405
1406 // Submit the form.
1407 mainSession.evaluateJS("document.querySelector('#form1').submit()")
1408 sessionRule.waitForResult(saveHandled1)
1409
1410 // Reload.
1411 val session2 = sessionRule.createOpenSession()
1412 session2.loadTestPath(FORMS3_HTML_PATH)
1413 session2.waitForPageStop()
1414
1415 session2.delegateDuringNextWait(object : Callbacks.PromptDelegate {
1416 @AssertCalled(count = 1)
1417 override fun onLoginSave(
1418 session: GeckoSession,
1419 prompt: AutocompleteRequest<LoginSaveOption>)
1420 : GeckoResult<PromptDelegate.PromptResponse>? {
1421 assertThat("Session should not be null", session, notNullValue())
1422
1423 val option = prompt.options[0]
1424 val login = option.value
1425
1426 assertThat("Login should not be null", login, notNullValue())
1427
1428 assertThat(
1429 "Username should match",
1430 login.username,
1431 equalTo(user2))
1432
1433 assertThat(
1434 "Password should match",
1435 login.password,
1436 equalTo(pass2))
1437
1438 return GeckoResult.fromValue(prompt.confirm(option))
1439 }
1440 })
1441
1442 // Assign alternative login credentials.
1443 session2.evaluateJS("document.querySelector('#user1').value = '$user2'")
1444 session2.evaluateJS("document.querySelector('#pass1').value = '$pass2'")
1445
1446 // Submit the form.
1447 session2.evaluateJS("document.querySelector('#form1').submit()")
1448 sessionRule.waitForResult(saveHandled2)
1449
1450 // Reload for the last time.
1451 val session3 = sessionRule.createOpenSession()
1452
1453 session3.delegateUntilTestEnd(object : Callbacks.PromptDelegate {
1454 @AssertCalled(count = 1)
1455 override fun onLoginSelect(
1456 session: GeckoSession,
1457 prompt: AutocompleteRequest<LoginSelectOption>)
1458 : GeckoResult<PromptDelegate.PromptResponse>? {
1459 assertThat("Session should not be null", session, notNullValue())
1460
1461 assertThat(
1462 "There should be two options",
1463 prompt.options.size,
1464 equalTo(2))
1465
1466 var usernames = arrayOf(user1, user2)
1467 var passwords = arrayOf(pass1, pass2)
1468
1469 for (i in 0..1) {
1470 val login = prompt.options[i].value
1471
1472 assertThat("Login should not be null", login, notNullValue())
1473 assertThat(
1474 "Username should match",
1475 login.username,
1476 equalTo(usernames[i]))
1477 assertThat(
1478 "Password should match",
1479 login.password,
1480 equalTo(passwords[i]))
1481 }
1482
1483 val login = prompt.options[0].value
1484 val modOption = LoginSelectOption(LoginEntry.Builder()
1485 .origin(login.origin)
1486 .formActionOrigin(login.formActionOrigin)
1487 .username(userMod)
1488 .password(passMod)
1489 .build())
1490
1491 Handler(Looper.getMainLooper()).postDelayed({
1492 selectHandled.complete(null)
1493 }, acceptDelay)
1494
1495 return GeckoResult.fromValue(prompt.confirm(modOption))
1496 }
1497 })
1498
1499 session3.loadTestPath(FORMS3_HTML_PATH)
1500 session3.waitForPageStop()
1501
1502 // Focus on the username input field.
1503 session3.evaluateJS("document.querySelector('#user1').focus()")
1504 sessionRule.waitForResult(selectHandled)
1505
1506 assertThat(
1507 "Filled username should match",
1508 session3.evaluateJS("document.querySelector('#user1').value") as String,
1509 equalTo(userMod))
1510
1511 assertThat(
1512 "Filled password should match",
1513 session3.evaluateJS("document.querySelector('#pass1').value") as String,
1514 equalTo(passMod))
1515
1516 // Submit the selection.
1517 session3.evaluateJS("document.querySelector('#form1').submit()")
1518 session3.waitForPageStop()
1519 }
1520
1521 @Test
1522 fun loginSelectGeneratedPassword() {
1523 sessionRule.setPrefsUntilTestEnd(mapOf(
1524 // Enable login management since it's disabled in automation.
1525 "signon.rememberSignons" to true,
1526 "signon.autofillForms.http" to true,
1527 "signon.generation.enabled" to true,
1528 "signon.generation.available" to true,
1529 "dom.disable_open_during_load" to false,
1530 "signon.userInputRequiredToCapture.enabled" to false))
1531
1532 // Test:
1533 // 1. Load a login form page.
1534 // 2. Input username.
1535 // 3. Focus on the password input field.
1536 // a. Ensure onLoginSelect is called with a generated password.
1537 // b. Return the login entry with the generated password.
1538 // 4. Submit the login form.
1539 // a. Ensure onLoginSave is called with accordingly.
1540
1541 val runtime = sessionRule.runtime
1542 val register = { delegate: StorageDelegate ->
1543 runtime.autocompleteStorageDelegate = delegate
1544 }
1545 val unregister = { _: StorageDelegate ->
1546 runtime.autocompleteStorageDelegate = null
1547 }
1548
1549 val user1 = "user1x"
1550 var genPass = ""
1551
1552 val saveHandled1 = GeckoResult<Void>()
1553 val selectHandled = GeckoResult<Void>()
1554 var numSelects = 0
1555
1556 sessionRule.addExternalDelegateUntilTestEnd(
1557 StorageDelegate::class, register, unregister,
1558 object : StorageDelegate {
1559 @AssertCalled
1560 override fun onLoginFetch(domain: String)
1561 : GeckoResult<Array<LoginEntry>>? {
1562 assertThat("Domain should match", domain, equalTo("localhost"))
1563
1564 return GeckoResult.fromValue(null)
1565 }
1566
1567 @AssertCalled(count = 1)
1568 override fun onLoginSave(login: LoginEntry) {
1569 assertThat(
1570 "Username should match",
1571 login.username,
1572 equalTo(user1))
1573
1574 assertThat(
1575 "Password should match",
1576 login.password,
1577 equalTo(genPass))
1578
1579 saveHandled1.complete(null)
1580 }
1581
1582 @AssertCalled(false)
1583 override fun onLoginUsed(login: LoginEntry, usedFields: Int) {}
1584 })
1585
1586 mainSession.loadTestPath(FORMS4_HTML_PATH)
1587 mainSession.waitForPageStop()
1588
1589 mainSession.delegateUntilTestEnd(object : Callbacks.PromptDelegate {
1590 @AssertCalled
1591 override fun onLoginSelect(
1592 session: GeckoSession,
1593 prompt: AutocompleteRequest<LoginSelectOption>)
1594 : GeckoResult<PromptDelegate.PromptResponse>? {
1595 assertThat("Session should not be null", session, notNullValue())
1596
1597 assertThat(
1598 "There should be one option",
1599 prompt.options.size,
1600 equalTo(1))
1601
1602 val option = prompt.options[0]
1603 val login = option.value
1604
1605 assertThat(
1606 "Hint should match",
1607 option.hint,
1608 equalTo(SelectOption.Hint.GENERATED))
1609
1610 assertThat("Login should not be null", login, notNullValue())
1611 assertThat(
1612 "Password should not be empty",
1613 login.password,
1614 not(isEmptyOrNullString()))
1615
1616 genPass = login.password
1617
1618 if (numSelects == 0) {
1619 Handler(Looper.getMainLooper()).postDelayed({
1620 selectHandled.complete(null)
1621 }, acceptDelay)
1622 }
1623 ++numSelects
1624
1625 return GeckoResult.fromValue(prompt.confirm(option))
1626 }
1627
1628 @AssertCalled(count = 1)
1629 override fun onLoginSave(
1630 session: GeckoSession,
1631 prompt: AutocompleteRequest<LoginSaveOption>)
1632 : GeckoResult<PromptDelegate.PromptResponse>? {
1633 assertThat("Session should not be null", session, notNullValue())
1634
1635 val option = prompt.options[0]
1636 val login = option.value
1637
1638 assertThat("Login should not be null", login, notNullValue())
1639
1640 assertThat(
1641 "Username should match",
1642 login.username,
1643 equalTo(user1))
1644
1645 // TODO: The flag is only set for login entry updates yet.
1646 /*
1647 assertThat(
1648 "Hint should match",
1649 option.hint,
1650 equalTo(LoginSaveOption.Hint.GENERATED))
1651 */
1652
1653 assertThat(
1654 "Password should not be empty",
1655 login.password,
1656 not(isEmptyOrNullString()))
1657
1658 assertThat(
1659 "Password should match",
1660 login.password,
1661 equalTo(genPass))
1662
1663 return GeckoResult.fromValue(prompt.confirm(option))
1664 }
1665 })
1666
1667 // Assign username and focus on password.
1668 mainSession.evaluateJS("document.querySelector('#user1').value = '$user1'")
1669 mainSession.evaluateJS("document.querySelector('#pass1').focus()")
1670 sessionRule.waitForResult(selectHandled)
1671
1672 assertThat(
1673 "Filled username should match",
1674 mainSession.evaluateJS("document.querySelector('#user1').value") as String,
1675 equalTo(user1))
1676
1677 val filledPass = mainSession.evaluateJS(
1678 "document.querySelector('#pass1').value") as String
1679
1680 assertThat(
1681 "Password should not be empty",
1682 filledPass,
1683 not(isEmptyOrNullString()))
1684
1685 assertThat(
1686 "Filled password should match",
1687 filledPass,
1688 equalTo(genPass))
1689
1690 // Submit the selection.
1691 mainSession.evaluateJS("document.querySelector('#form1').submit()")
1692 mainSession.waitForPageStop()
1693 }
1694 }
1695