Source code
Revision control
Copy as Markdown
Other Tools
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
* Any copyright is dedicated to the Public Domain.
package org.mozilla.geckoview.test
import android.os.Handler
import android.os.Looper
import android.view.KeyEvent
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import org.hamcrest.Matchers.equalTo
import org.hamcrest.Matchers.isEmptyOrNullString
import org.hamcrest.Matchers.not
import org.hamcrest.Matchers.notNullValue
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.geckoview.Autocomplete.Address
import org.mozilla.geckoview.Autocomplete.AddressSelectOption
import org.mozilla.geckoview.Autocomplete.CreditCard
import org.mozilla.geckoview.Autocomplete.CreditCardSaveOption
import org.mozilla.geckoview.Autocomplete.CreditCardSelectOption
import org.mozilla.geckoview.Autocomplete.LoginEntry
import org.mozilla.geckoview.Autocomplete.LoginSaveOption
import org.mozilla.geckoview.Autocomplete.LoginSelectOption
import org.mozilla.geckoview.Autocomplete.SelectOption
import org.mozilla.geckoview.Autocomplete.StorageDelegate
import org.mozilla.geckoview.Autocomplete.UsedField
import org.mozilla.geckoview.GeckoResult
import org.mozilla.geckoview.GeckoSession
import org.mozilla.geckoview.GeckoSession.PromptDelegate
import org.mozilla.geckoview.GeckoSession.PromptDelegate.AutocompleteRequest
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.AssertCalled
@RunWith(AndroidJUnit4::class)
@MediumTest
class AutocompleteTest : BaseSessionTest() {
val acceptDelay: Long = 100
// This is a utility to delete previous credit card and address information.
// Some credit card tests may not use fetched data since pop up is opened
// before fetching it.
private fun clearData() {
mainSession.loadTestPath(ADDRESS_FORM_HTML_PATH)
mainSession.waitForPageStop()
val fetchHandled = GeckoResult<Void>()
sessionRule.delegateDuringNextWait(object : StorageDelegate {
override fun onAddressFetch(): GeckoResult<Array<Address>>? {
return null
}
override fun onCreditCardFetch(): GeckoResult<Array<CreditCard>>? {
Handler(Looper.getMainLooper()).postDelayed({
fetchHandled.complete(null)
}, acceptDelay)
return null
}
})
mainSession.evaluateJS("document.querySelector('#name').focus()")
sessionRule.waitForResult(fetchHandled)
}
@Test
fun loginBuilderDefaultValue() {
val login = LoginEntry.Builder()
.build()
assertThat(
"Guid should match",
login.guid,
equalTo(null),
)
assertThat(
"Origin should match",
login.origin,
equalTo(""),
)
assertThat(
"Form action origin should match",
login.formActionOrigin,
equalTo(null),
)
assertThat(
"HTTP realm should match",
login.httpRealm,
equalTo(null),
)
assertThat(
"Username should match",
login.username,
equalTo(""),
)
assertThat(
"Password should match",
login.password,
equalTo(""),
)
}
@Test
fun fetchLogins() {
sessionRule.setPrefsUntilTestEnd(
mapOf(
// Enable login management since it's disabled in automation.
"signon.rememberSignons" to true,
"signon.autofillForms.http" to true,
),
)
val fetchHandled = GeckoResult<Void>()
sessionRule.delegateDuringNextWait(object : StorageDelegate {
@AssertCalled(count = 1)
override fun onLoginFetch(domain: String): GeckoResult<Array<LoginEntry>>? {
assertThat("Domain should match", domain, equalTo("localhost"))
Handler(Looper.getMainLooper()).postDelayed({
fetchHandled.complete(null)
}, acceptDelay)
return null
}
})
mainSession.loadTestPath(FORMS3_HTML_PATH)
sessionRule.waitForResult(fetchHandled)
}
@Test
fun fetchCreditCards() {
val fetchHandled = GeckoResult<Void>()
mainSession.loadTestPath(CC_FORM_HTML_PATH)
mainSession.waitForPageStop()
sessionRule.delegateDuringNextWait(object : StorageDelegate {
@AssertCalled(count = 1)
override fun onCreditCardFetch(): GeckoResult<Array<CreditCard>>? {
Handler(Looper.getMainLooper()).postDelayed({
fetchHandled.complete(null)
}, acceptDelay)
return null
}
})
mainSession.evaluateJS("document.querySelector('#name').focus()")
sessionRule.waitForResult(fetchHandled)
}
@Test
fun creditCardBuilderDefaultValue() {
val creditCard = CreditCard.Builder()
.build()
assertThat(
"Guid should match",
creditCard.guid,
equalTo(null),
)
assertThat(
"Name should match",
creditCard.name,
equalTo(""),
)
assertThat(
"Number should match",
creditCard.number,
equalTo(""),
)
assertThat(
"Expiration month should match",
creditCard.expirationMonth,
equalTo(""),
)
assertThat(
"Expiration year should match",
creditCard.expirationYear,
equalTo(""),
)
}
@Test
fun creditCardSelectAndFill() {
// Workaround to fetch and open prompt
clearData()
// Test:
// 1. Load a credit card form page.
// 2. Focus on the name input field.
// a. Ensure onCreditCardFetch is called.
// b. Return the saved entries.
// c. Ensure onCreditCardSelect is called.
// d. Select and return one of the options.
// e. Ensure the form is filled accordingly.
val name = arrayOf("Peter Parker", "John Doe")
val number = arrayOf("1234-1234-1234-1234", "2345-2345-2345-2345")
val guid = arrayOf("test-guid1", "test-guid2")
val expMonth = arrayOf("04", "08")
val expYear = arrayOf("22", "23")
val savedCC = arrayOf(
CreditCard.Builder()
.guid(guid[0])
.name(name[0])
.number(number[0])
.expirationMonth(expMonth[0])
.expirationYear(expYear[0])
.build(),
CreditCard.Builder()
.guid(guid[1])
.name(name[1])
.number(number[1])
.expirationMonth(expMonth[1])
.expirationYear(expYear[1])
.build(),
)
val selectHandled = GeckoResult<Void>()
mainSession.loadTestPath(CC_FORM_HTML_PATH)
mainSession.waitForPageStop()
sessionRule.delegateDuringNextWait(object : StorageDelegate {
@AssertCalled
override fun onCreditCardFetch(): GeckoResult<Array<CreditCard>>? {
return GeckoResult.fromValue(savedCC)
}
@AssertCalled(false)
override fun onCreditCardSave(creditCard: CreditCard) {}
})
mainSession.delegateUntilTestEnd(object : PromptDelegate {
@AssertCalled(count = 1)
override fun onCreditCardSelect(
session: GeckoSession,
prompt: AutocompleteRequest<CreditCardSelectOption>,
): GeckoResult<PromptDelegate.PromptResponse>? {
assertThat("Session should not be null", session, notNullValue())
assertThat(
"There should be two options",
prompt.options.size,
equalTo(2),
)
for (i in 0..1) {
val creditCard = prompt.options[i].value
assertThat("Credit card should not be null", creditCard, notNullValue())
assertThat(
"Name should match",
creditCard.name,
equalTo(name[i]),
)
assertThat(
"Number should match",
creditCard.number,
equalTo(number[i]),
)
assertThat(
"Expiration month should match",
creditCard.expirationMonth,
equalTo(expMonth[i]),
)
assertThat(
"Expiration year should match",
creditCard.expirationYear,
equalTo(expYear[i]),
)
}
Handler(Looper.getMainLooper()).postDelayed({
selectHandled.complete(null)
}, acceptDelay)
return GeckoResult.fromValue(prompt.confirm(prompt.options[0]))
}
})
// Focus on the name input field.
mainSession.evaluateJS("document.querySelector('#name').focus()")
sessionRule.waitForResult(selectHandled)
assertThat(
"Filled name should match",
mainSession.evaluateJS("document.querySelector('#name').value") as String,
equalTo(name[0]),
)
assertThat(
"Filled number should match",
mainSession.evaluateJS("document.querySelector('#number').value") as String,
equalTo(number[0]),
)
assertThat(
"Filled expiration month should match",
mainSession.evaluateJS("document.querySelector('#expMonth').value") as String,
equalTo(expMonth[0]),
)
assertThat(
"Filled expiration year should match",
mainSession.evaluateJS("document.querySelector('#expYear').value") as String,
equalTo(expYear[0]),
)
}
@Test
fun addressBuilderDefaultValue() {
val address = Address.Builder()
.build()
assertThat(
"Guid should match",
address.guid,
equalTo(null),
)
assertThat(
"Name should match",
address.name,
equalTo(""),
)
assertThat(
"Given name should match",
address.givenName,
equalTo(""),
)
assertThat(
"Family name should match",
address.familyName,
equalTo(""),
)
assertThat(
"Street address should match",
address.streetAddress,
equalTo(""),
)
assertThat(
"Address level 1 should match",
address.addressLevel1,
equalTo(""),
)
assertThat(
"Address level 2 should match",
address.addressLevel2,
equalTo(""),
)
assertThat(
"Address level 3 should match",
address.addressLevel3,
equalTo(""),
)
assertThat(
"Postal code should match",
address.postalCode,
equalTo(""),
)
assertThat(
"Country should match",
address.country,
equalTo(""),
)
assertThat(
"Tel should match",
address.tel,
equalTo(""),
)
assertThat(
"Email should match",
address.email,
equalTo(""),
)
}
@Test
fun creditCardSelectDismiss() {
// Workaround to fetch and open prompt
clearData()
val name = arrayOf("Peter Parker", "John Doe", "Taro Yamada")
val number = arrayOf("1234-1234-1234-1234", "2345-2345-2345-2345", "5555-5555-5555-5555")
val guid = arrayOf("test-guid1", "test-guid2", "test-guid3")
val expMonth = arrayOf("04", "08", "12")
val expYear = arrayOf("22", "23", "24")
val savedCC = arrayOf(
CreditCard.Builder()
.guid(guid[0])
.name(name[0])
.number(number[0])
.expirationMonth(expMonth[0])
.expirationYear(expYear[0])
.build(),
CreditCard.Builder()
.guid(guid[1])
.name(name[1])
.number(number[1])
.expirationMonth(expMonth[1])
.expirationYear(expYear[1])
.build(),
CreditCard.Builder()
.guid(guid[2])
.name(name[2])
.number(number[2])
.expirationMonth(expMonth[2])
.expirationYear(expYear[2])
.build(),
)
mainSession.loadTestPath(CC_FORM_HTML_PATH)
mainSession.waitForPageStop()
sessionRule.delegateUntilTestEnd(object : StorageDelegate {
@AssertCalled
override fun onCreditCardFetch(): GeckoResult<Array<CreditCard>>? {
return GeckoResult.fromValue(savedCC)
}
})
val result = GeckoResult<PromptDelegate.PromptResponse>()
val promptInstanceDelegate = object : PromptDelegate.PromptInstanceDelegate {
override fun onPromptDismiss(prompt: PromptDelegate.BasePrompt) {
result.complete(prompt.dismiss())
}
}
val promptHandled = GeckoResult<Void>()
mainSession.delegateUntilTestEnd(object : PromptDelegate {
@AssertCalled
override fun onCreditCardSelect(session: GeckoSession, prompt: AutocompleteRequest<CreditCardSelectOption>): GeckoResult<PromptDelegate.PromptResponse>? {
assertThat(
"There should be three options",
prompt.options.size,
equalTo(3),
)
prompt.setDelegate(promptInstanceDelegate)
Handler(Looper.getMainLooper()).postDelayed({
promptHandled.complete(null)
}, acceptDelay)
return GeckoResult()
}
})
mainSession.evaluateJS("document.querySelector('#name').focus()")
sessionRule.waitForResult(promptHandled)
mainSession.evaluateJS("document.querySelector('#name').blur()")
sessionRule.waitForResult(result)
}
@Test
fun fetchAddresses() {
val fetchHandled = GeckoResult<Void>()
sessionRule.delegateUntilTestEnd(object : StorageDelegate {
@AssertCalled(count = 1)
override fun onAddressFetch(): GeckoResult<Array<Address>>? {
Handler(Looper.getMainLooper()).postDelayed({
fetchHandled.complete(null)
}, acceptDelay)
return null
}
})
mainSession.loadTestPath(ADDRESS_FORM_HTML_PATH)
mainSession.waitForPageStop()
mainSession.evaluateJS("document.querySelector('#name').focus()")
sessionRule.waitForResult(fetchHandled)
}
fun checkAddressesForCorrectness(savedAddresses: Array<Address>, selectedAddress: Address) {
// Test:
// 1. Load an address form page.
// 2. Focus on the given name input field.
// a. Ensure onAddressFetch is called.
// b. Return the saved entries.
// c. Ensure onAddressSelect is called.
// d. Select and return one of the options.
// e. Ensure the form is filled accordingly.
val selectHandled = GeckoResult<Void>()
sessionRule.delegateUntilTestEnd(object : StorageDelegate {
@AssertCalled
override fun onAddressFetch(): GeckoResult<Array<Address>>? {
return GeckoResult.fromValue(savedAddresses)
}
@AssertCalled(false)
override fun onAddressSave(address: Address) {}
})
mainSession.delegateUntilTestEnd(object : PromptDelegate {
@AssertCalled(count = 1)
override fun onAddressSelect(
session: GeckoSession,
prompt: AutocompleteRequest<AddressSelectOption>,
): GeckoResult<PromptDelegate.PromptResponse>? {
assertThat("Session should not be null", session, notNullValue())
assertThat(
"There should be one option",
prompt.options.size,
equalTo(savedAddresses.size),
)
val addressOption = prompt.options.find { it.value.familyName == selectedAddress.familyName }
val address = addressOption?.value
assertThat("Address should not be null", address, notNullValue())
assertThat(
"Guid should match",
address?.guid,
equalTo(selectedAddress.guid),
)
assertThat(
"Name should match",
address?.name,
equalTo(selectedAddress.name),
)
assertThat(
"Given name should match",
address?.givenName,
equalTo(selectedAddress.givenName),
)
assertThat(
"Family name should match",
address?.familyName,
equalTo(selectedAddress.familyName),
)
assertThat(
"Street address should match",
address?.streetAddress,
equalTo(selectedAddress.streetAddress),
)
assertThat(
"Address level 1 should match",
address?.addressLevel1,
equalTo(selectedAddress.addressLevel1),
)
assertThat(
"Address level 2 should match",
address?.addressLevel2,
equalTo(selectedAddress.addressLevel2),
)
assertThat(
"Address level 3 should match",
address?.addressLevel3,
equalTo(selectedAddress.addressLevel3),
)
assertThat(
"Postal code should match",
address?.postalCode,
equalTo(selectedAddress.postalCode),
)
assertThat(
"Country should match",
address?.country,
equalTo(selectedAddress.country),
)
assertThat(
"Tel should match",
address?.tel,
equalTo(selectedAddress.tel),
)
assertThat(
"Email should match",
address?.email,
equalTo(selectedAddress.email),
)
Handler(Looper.getMainLooper()).postDelayed({
selectHandled.complete(null)
}, acceptDelay)
return GeckoResult.fromValue(prompt.confirm(addressOption!!))
}
})
mainSession.loadTestPath(ADDRESS_FORM_HTML_PATH)
mainSession.waitForPageStop()
// Focus on the given name input field.
mainSession.evaluateJS("document.querySelector('#givenName').focus()")
sessionRule.waitForResult(selectHandled)
assertThat(
"Filled given name should match",
mainSession.evaluateJS("document.querySelector('#givenName').value") as String,
equalTo(selectedAddress.givenName),
)
assertThat(
"Filled family name should match",
mainSession.evaluateJS("document.querySelector('#familyName').value") as String,
equalTo(selectedAddress.familyName),
)
assertThat(
"Filled street address should match",
mainSession.evaluateJS("document.querySelector('#streetAddress').value") as String,
equalTo(selectedAddress.streetAddress),
)
assertThat(
"Filled country should match",
mainSession.evaluateJS("document.querySelector('#country').value") as String,
equalTo(selectedAddress.country),
)
assertThat(
"Filled postal code should match",
mainSession.evaluateJS("document.querySelector('#postalCode').value") as String,
equalTo(selectedAddress.postalCode),
)
assertThat(
"Filled email should match",
mainSession.evaluateJS("document.querySelector('#email').value") as String,
equalTo(selectedAddress.email),
)
assertThat(
"Filled telephone number should match",
mainSession.evaluateJS("document.querySelector('#tel').value") as String,
equalTo(selectedAddress.tel),
)
assertThat(
"Filled organization should match",
mainSession.evaluateJS("document.querySelector('#organization').value") as String,
equalTo(selectedAddress.organization),
)
}
@Test
fun addressSelectAndFill() {
val name = "Peter Parker"
val givenName = "Peter"
val familyName = "Parker"
val streetAddress = "20 Ingram Street, Forest Hills Gardens, Queens"
val postalCode = "11375"
val country = "US"
val email = "spiderman@newyork.com"
val tel = "+1 180090021"
val organization = ""
val guid = "test-guid"
val savedAddress = Address.Builder()
.guid(guid)
.name(name)
.givenName(givenName)
.familyName(familyName)
.streetAddress(streetAddress)
.postalCode(postalCode)
.country(country)
.email(email)
.tel(tel)
.organization(organization)
.build()
val savedAddresses = mutableListOf<Address>(savedAddress)
checkAddressesForCorrectness(savedAddresses.toTypedArray(), savedAddress)
}
@Test
fun addressSelectAndFillMultipleAddresses() {
val names = arrayOf("Peter Parker", "Wade Wilson")
val givenNames = arrayOf("Peter", "Wade")
val familyNames = arrayOf("Parker", "Wilson")
val streetAddresses = arrayOf("20 Ingram Street, Forest Hills Gardens, Queens", "890 Fifth Avenue, Manhattan")
val postalCodes = arrayOf("11375", "10110")
val countries = arrayOf("US", "US")
val emails = arrayOf("spiderman@newyork.com", "deadpool@newyork.com")
val tels = arrayOf("+1 180090021", "+1 180055555")
val organizations = arrayOf("", "")
val guids = arrayOf("test-guid-1", "test-guid-2")
val selectedAddress = Address.Builder()
.guid(guids[1])
.name(names[1])
.givenName(givenNames[1])
.familyName(familyNames[1])
.streetAddress(streetAddresses[1])
.postalCode(postalCodes[1])
.country(countries[1])
.email(emails[1])
.tel(tels[1])
.organization(organizations[1])
.build()
val savedAddresses = mutableListOf<Address>(
Address.Builder()
.guid(guids[0])
.name(names[0])
.givenName(givenNames[0])
.familyName(familyNames[0])
.streetAddress(streetAddresses[0])
.postalCode(postalCodes[0])
.country(countries[0])
.email(emails[0])
.tel(tels[0])
.organization(organizations[0])
.build(),
selectedAddress,
)
checkAddressesForCorrectness(savedAddresses.toTypedArray(), selectedAddress)
}
@Test
fun addressSelectDismiss() {
val name = "Peter Parker"
val givenName = "Peter"
val familyName = "Parker"
val streetAddress = "20 Ingram Street, Forest Hills Gardens, Queens"
val postalCode = "11375"
val country = "US"
val email = "spiderman@newyork.com"
val tel = "+1 180090021"
val organization = ""
val guid = "test-guid"
val savedAddress = Address.Builder()
.guid(guid)
.name(name)
.givenName(givenName)
.familyName(familyName)
.streetAddress(streetAddress)
.postalCode(postalCode)
.country(country)
.email(email)
.tel(tel)
.organization(organization)
.build()
val savedAddresses = mutableListOf<Address>(savedAddress)
sessionRule.delegateUntilTestEnd(object : StorageDelegate {
@AssertCalled
override fun onAddressFetch(): GeckoResult<Array<Address>>? {
return GeckoResult.fromValue(savedAddresses.toTypedArray())
}
})
val result = GeckoResult<PromptDelegate.PromptResponse>()
val promptInstanceDelegate = object : PromptDelegate.PromptInstanceDelegate {
override fun onPromptDismiss(prompt: PromptDelegate.BasePrompt) {
result.complete(prompt.dismiss())
}
}
val promptHandled = GeckoResult<Void>()
mainSession.delegateUntilTestEnd(object : PromptDelegate {
@AssertCalled
override fun onAddressSelect(session: GeckoSession, prompt: AutocompleteRequest<AddressSelectOption>): GeckoResult<PromptDelegate.PromptResponse>? {
assertThat(
"There should be one option",
prompt.options.size,
equalTo(1),
)
prompt.setDelegate(promptInstanceDelegate)
Handler(Looper.getMainLooper()).postDelayed({
promptHandled.complete(null)
}, acceptDelay)
return GeckoResult()
}
})
mainSession.loadTestPath(ADDRESS_FORM_HTML_PATH)
mainSession.waitForPageStop()
mainSession.evaluateJS("document.querySelector('#givenName').focus()")
sessionRule.waitForResult(promptHandled)
mainSession.evaluateJS("document.querySelector('#givenName').blur()")
sessionRule.waitForResult(result)
}
@Test
fun loginSaveDismiss() {
sessionRule.setPrefsUntilTestEnd(
mapOf(
// Enable login management since it's disabled in automation.
"signon.rememberSignons" to true,
"signon.autofillForms.http" to true,
"signon.userInputRequiredToCapture.enabled" to false,
),
)
sessionRule.delegateDuringNextWait(object : StorageDelegate {
@AssertCalled(count = 1)
override fun onLoginFetch(domain: String): GeckoResult<Array<LoginEntry>>? {
assertThat("Domain should match", domain, equalTo("localhost"))
return null
}
})
mainSession.loadTestPath(FORMS3_HTML_PATH)
mainSession.waitForPageStop()
sessionRule.delegateUntilTestEnd(object : StorageDelegate {
@AssertCalled(count = 0)
override fun onLoginSave(login: LoginEntry) {}
})
// Assign login credentials.
mainSession.evaluateJS("document.querySelector('#user1').value = 'user1x'")
mainSession.evaluateJS("document.querySelector('#pass1').value = 'pass1x'")
// Submit the form.
mainSession.evaluateJS("document.querySelector('#form1').submit()")
sessionRule.waitUntilCalled(object : PromptDelegate {
@AssertCalled(count = 1)
override fun onLoginSave(
session: GeckoSession,
prompt: AutocompleteRequest<LoginSaveOption>,
): GeckoResult<PromptDelegate.PromptResponse>? {
val option = prompt.options[0]
val login = option.value
assertThat("Session should not be null", session, notNullValue())
assertThat("Login should not be null", login, notNullValue())
assertThat(
"Username should match",
login.username,
equalTo("user1x"),
)
assertThat(
"Password should match",
login.password,
equalTo("pass1x"),
)
return GeckoResult.fromValue(prompt.dismiss())
}
})
}
@Test
fun loginSaveAccept() {
sessionRule.setPrefsUntilTestEnd(
mapOf(
// Enable login management since it's disabled in automation.
"signon.rememberSignons" to true,
"signon.autofillForms.http" to true,
"signon.userInputRequiredToCapture.enabled" to false,
),
)
mainSession.loadTestPath(FORMS3_HTML_PATH)
mainSession.waitForPageStop()
val saveHandled = GeckoResult<Void>()
sessionRule.delegateUntilTestEnd(object : StorageDelegate {
@AssertCalled
override fun onLoginSave(login: LoginEntry) {
assertThat(
"Username should match",
login.username,
equalTo("user1x"),
)
assertThat(
"Password should match",
login.password,
equalTo("pass1x"),
)
saveHandled.complete(null)
}
})
sessionRule.delegateDuringNextWait(object : PromptDelegate {
@AssertCalled(count = 1)
override fun onLoginSave(
session: GeckoSession,
prompt: AutocompleteRequest<LoginSaveOption>,
): GeckoResult<PromptDelegate.PromptResponse>? {
assertThat("Session should not be null", session, notNullValue())
val option = prompt.options[0]
val login = option.value
assertThat("Login should not be null", login, notNullValue())
assertThat(
"Username should match",
login.username,
equalTo("user1x"),
)
assertThat(
"Password should match",
login.password,
equalTo("pass1x"),
)
return GeckoResult.fromValue(prompt.confirm(option))
}
})
// Assign login credentials.
mainSession.evaluateJS("document.querySelector('#user1').value = 'user1x'")
mainSession.evaluateJS("document.querySelector('#pass1').value = 'pass1x'")
// Submit the form.
mainSession.evaluateJS("document.querySelector('#form1').submit()")
sessionRule.waitForResult(saveHandled)
}
@Test
fun loginSaveModifyAccept() {
sessionRule.setPrefsUntilTestEnd(
mapOf(
// Enable login management since it's disabled in automation.
"signon.rememberSignons" to true,
"signon.autofillForms.http" to true,
"signon.userInputRequiredToCapture.enabled" to false,
),
)
mainSession.loadTestPath(FORMS3_HTML_PATH)
mainSession.waitForPageStop()
val saveHandled = GeckoResult<Void>()
sessionRule.delegateUntilTestEnd(object : StorageDelegate {
@AssertCalled
override fun onLoginSave(login: LoginEntry) {
assertThat(
"Username should match",
login.username,
equalTo("user1x"),
)
assertThat(
"Password should match",
login.password,
equalTo("pass1xmod"),
)
saveHandled.complete(null)
}
})
sessionRule.delegateDuringNextWait(object : PromptDelegate {
@AssertCalled(count = 1)
override fun onLoginSave(
session: GeckoSession,
prompt: AutocompleteRequest<LoginSaveOption>,
): GeckoResult<PromptDelegate.PromptResponse>? {
assertThat("Session should not be null", session, notNullValue())
val option = prompt.options[0]
val login = option.value
assertThat("Login should not be null", login, notNullValue())
assertThat(
"Username should match",
login.username,
equalTo("user1x"),
)
assertThat(
"Password should match",
login.password,
equalTo("pass1x"),
)
val modLogin = LoginEntry.Builder()
.origin(login.origin)
.formActionOrigin(login.origin)
.httpRealm(login.httpRealm)
.username(login.username)
.password("pass1xmod")
.build()
return GeckoResult.fromValue(prompt.confirm(LoginSaveOption(modLogin)))
}
})
// Assign login credentials.
mainSession.evaluateJS("document.querySelector('#user1').value = 'user1x'")
mainSession.evaluateJS("document.querySelector('#pass1').value = 'pass1x'")
// Submit the form.
mainSession.evaluateJS("document.querySelector('#form1').submit()")
sessionRule.waitForResult(saveHandled)
}
@Test
fun loginUpdateAccept() {
sessionRule.setPrefsUntilTestEnd(
mapOf(
// Enable login management since it's disabled in automation.
"signon.rememberSignons" to true,
"signon.autofillForms.http" to true,
"signon.userInputRequiredToCapture.enabled" to false,
),
)
val saveHandled = GeckoResult<Void>()
val saveHandled2 = GeckoResult<Void>()
val user1 = "user1x"
val pass1 = "pass1x"
val pass2 = "pass1up"
val guid = "test-guid"
val savedLogins = mutableListOf<LoginEntry>()
sessionRule.delegateUntilTestEnd(object : StorageDelegate {
@AssertCalled
override fun onLoginFetch(domain: String): GeckoResult<Array<LoginEntry>>? {
assertThat("Domain should match", domain, equalTo("localhost"))
return GeckoResult.fromValue(savedLogins.toTypedArray())
}
@AssertCalled(count = 2)
override fun onLoginSave(login: LoginEntry) {
assertThat(
"Username should match",
login.username,
equalTo(user1),
)
assertThat(
"Password should match",
login.password,
equalTo(forEachCall(pass1, pass2)),
)
assertThat(
"GUID should match",
login.guid,
equalTo(forEachCall(null, guid)),
)
val savedLogin = LoginEntry.Builder()
.guid(guid)
.origin(login.origin)
.formActionOrigin(login.formActionOrigin)
.username(login.username)
.password(login.password)
.build()
savedLogins.add(savedLogin)
if (sessionRule.currentCall.counter == 1) {
saveHandled.complete(null)
} else if (sessionRule.currentCall.counter == 2) {
saveHandled2.complete(null)
}
}
})
sessionRule.delegateUntilTestEnd(object : PromptDelegate {
@AssertCalled(count = 2)
override fun onLoginSave(
session: GeckoSession,
prompt: AutocompleteRequest<LoginSaveOption>,
): GeckoResult<PromptDelegate.PromptResponse>? {
assertThat("Session should not be null", session, notNullValue())
val option = prompt.options[0]
val login = option.value
assertThat("Login should not be null", login, notNullValue())
assertThat(
"Username should match",
login.username,
equalTo(user1),
)
assertThat(
"Password should match",
login.password,
equalTo(forEachCall(pass1, pass2)),
)
return GeckoResult.fromValue(prompt.confirm(option))
}
})
// Assign login credentials.
mainSession.loadTestPath(FORMS3_HTML_PATH)
mainSession.waitForPageStop()
mainSession.evaluateJS("document.querySelector('#user1').value = '$user1'")
mainSession.evaluateJS("document.querySelector('#pass1').value = '$pass1'")
mainSession.evaluateJS("document.querySelector('#form1').submit()")
sessionRule.waitForResult(saveHandled)
// Update login credentials.
val session2 = sessionRule.createOpenSession()
session2.loadTestPath(FORMS3_HTML_PATH)
session2.waitForPageStop()
session2.evaluateJS("document.querySelector('#pass1').value = '$pass2'")
session2.evaluateJS("document.querySelector('#form1').submit()")
sessionRule.waitForResult(saveHandled2)
}
@Test
fun creditCardSaveAccept() {
val ccName = "MyCard"
val ccNumber = "5105105105105100"
val ccExpMonth = "6"
val ccExpYear = "2024"
mainSession.loadTestPath(CC_FORM_HTML_PATH)
mainSession.waitForPageStop()
val saveHandled = GeckoResult<Void>()
sessionRule.delegateUntilTestEnd(object : StorageDelegate {
@AssertCalled
override fun onCreditCardSave(creditCard: CreditCard) {
assertThat("Credit card name should match", creditCard.name, equalTo(ccName))
assertThat("Credit card number should match", creditCard.number, equalTo(ccNumber))
assertThat("Credit card expiration month should match", creditCard.expirationMonth, equalTo(ccExpMonth))
assertThat("Credit card expiration year should match", creditCard.expirationYear, equalTo(ccExpYear))
saveHandled.complete(null)
}
})
sessionRule.delegateDuringNextWait(object : PromptDelegate {
@AssertCalled
override fun onCreditCardSave(
session: GeckoSession,
request: AutocompleteRequest<CreditCardSaveOption>,
): GeckoResult<PromptDelegate.PromptResponse> {
assertThat("Session should not be null", session, notNullValue())
val option = request.options[0]
val cc = option.value
assertThat("Credit card should not be null", cc, notNullValue())
assertThat(
"Credit card name should match",
cc.name,
equalTo(ccName),
)
assertThat(
"Credit card number should match",
cc.number,
equalTo(ccNumber),
)
assertThat(
"Credit card expiration month should match",
cc.expirationMonth,
equalTo(ccExpMonth),
)
assertThat(
"Credit card expiration year should match",
cc.expirationYear,
equalTo(ccExpYear),
)
return GeckoResult.fromValue(request.confirm(option))
}
})
// Enter the card values
mainSession.evaluateJS("document.querySelector('#name').value = '$ccName'")
mainSession.evaluateJS("document.querySelector('#name').focus()")
mainSession.evaluateJS("document.querySelector('#number').value = '$ccNumber'")
mainSession.evaluateJS("document.querySelector('#number').focus()")
mainSession.evaluateJS("document.querySelector('#expMonth').value = '$ccExpMonth'")
mainSession.evaluateJS("document.querySelector('#expMonth').focus()")
mainSession.evaluateJS("document.querySelector('#expYear').value = '$ccExpYear'")
mainSession.evaluateJS("document.querySelector('#expYear').focus()")
// Submit the form
mainSession.evaluateJS("document.querySelector('form').requestSubmit()")
sessionRule.waitForResult(saveHandled)
}
@Test
fun creditCardSaveAcceptForm2() {
// the expected result.
val ccName = "MyCard"
val ccNumber = "5105105105105100"
val ccExpMonth = "6"
val ccExpYear = "2024"
mainSession.loadTestPath(CC_FORM_HTML_PATH)
mainSession.waitForPageStop()
val saveHandled = GeckoResult<Void>()
sessionRule.delegateUntilTestEnd(object : StorageDelegate {
@AssertCalled
override fun onCreditCardSave(creditCard: CreditCard) {
assertThat("Credit card name should match", creditCard.name, equalTo(ccName))
assertThat("Credit card number should match", creditCard.number, equalTo(ccNumber))
assertThat("Credit card expiration month should match", creditCard.expirationMonth, equalTo(ccExpMonth))
assertThat("Credit card expiration year should match", creditCard.expirationYear, equalTo(ccExpYear))
saveHandled.complete(null)
}
})
sessionRule.delegateDuringNextWait(object : PromptDelegate {
@AssertCalled
override fun onCreditCardSave(
session: GeckoSession,
request: AutocompleteRequest<CreditCardSaveOption>,
): GeckoResult<PromptDelegate.PromptResponse> {
assertThat("Session should not be null", session, notNullValue())
val option = request.options[0]
val cc = option.value
assertThat("Credit card should not be null", cc, notNullValue())
assertThat(
"Credit card name should match",
cc.name,
equalTo(ccName),
)
assertThat(
"Credit card number should match",
cc.number,
equalTo(ccNumber),
)
assertThat(
"Credit card expiration month should match",
cc.expirationMonth,
equalTo(ccExpMonth),
)
assertThat(
"Credit card expiration year should match",
cc.expirationYear,
equalTo(ccExpYear),
)
return GeckoResult.fromValue(request.confirm(option))
}
})
// Enter the card values
mainSession.evaluateJS("document.querySelector('#form2 #name').value = '$ccName'")
mainSession.evaluateJS("document.querySelector('#form2 #name').focus()")
mainSession.evaluateJS("document.querySelector('#form2 #number').value = '$ccNumber'")
mainSession.evaluateJS("document.querySelector('#form2 #number').focus()")
mainSession.evaluateJS("document.querySelector('#form2 #exp').value = '$ccExpMonth/$ccExpYear'")
mainSession.evaluateJS("document.querySelector('#form2 #exp').focus()")
// Submit the form
mainSession.evaluateJS("document.querySelector('#form2').requestSubmit()")
sessionRule.waitForResult(saveHandled)
}
@Test
fun creditCardSaveDismiss() {
val ccName = "MyCard"
val ccNumber = "5105105105105100"
val ccExpMonth = "6"
val ccExpYear = "2024"
mainSession.loadTestPath(CC_FORM_HTML_PATH)
mainSession.waitForPageStop()
sessionRule.delegateDuringNextWait(object : StorageDelegate {
@AssertCalled
override fun onCreditCardFetch(): GeckoResult<Array<CreditCard>>? {
return null
}
})
sessionRule.delegateUntilTestEnd(object : StorageDelegate {
@AssertCalled(count = 0)
override fun onCreditCardSave(creditCard: CreditCard) {}
})
// Enter the card values
mainSession.evaluateJS("document.querySelector('#name').value = '$ccName'")
mainSession.evaluateJS("document.querySelector('#name').focus()")
mainSession.evaluateJS("document.querySelector('#number').value = '$ccNumber'")
mainSession.evaluateJS("document.querySelector('#number').focus()")
mainSession.evaluateJS("document.querySelector('#expMonth').value = '$ccExpMonth'")
mainSession.evaluateJS("document.querySelector('#expMonth').focus()")
mainSession.evaluateJS("document.querySelector('#expYear').value = '$ccExpYear'")
mainSession.evaluateJS("document.querySelector('#expYear').focus()")
// Submit the form
mainSession.evaluateJS("document.querySelector('form').requestSubmit()")
sessionRule.waitUntilCalled(object : PromptDelegate {
@AssertCalled
override fun onCreditCardSave(
session: GeckoSession,
request: AutocompleteRequest<CreditCardSaveOption>,
): GeckoResult<PromptDelegate.PromptResponse> {
assertThat("Session should not be null", session, notNullValue())
val option = request.options[0]
val cc = option.value
assertThat("Credit card should not be null", cc, notNullValue())
assertThat(
"Credit card name should match",
cc.name,
equalTo(ccName),
)
assertThat(
"Credit card number should match",
cc.number,
equalTo(ccNumber),
)
assertThat(
"Credit card expiration month should match",
cc.expirationMonth,
equalTo(ccExpMonth),
)
assertThat(
"Credit card expiration year should match",
cc.expirationYear,
equalTo(ccExpYear),
)
return GeckoResult.fromValue(request.dismiss())
}
})
}
@Test
fun creditCardSaveModifyAccept() {
val ccName = "MyCard"
val ccNumber = "5105105105105100"
val ccExpMonth = "6"
val ccExpYearNew = "2026"
val ccExpYear = "2024"
mainSession.loadTestPath(CC_FORM_HTML_PATH)
mainSession.waitForPageStop()
val saveHandled = GeckoResult<Void>()
sessionRule.delegateUntilTestEnd(object : StorageDelegate {
@AssertCalled
override fun onCreditCardSave(creditCard: CreditCard) {
assertThat("Credit card name should match", creditCard.name, equalTo(ccName))
assertThat("Credit card number should match", creditCard.number, equalTo(ccNumber))
assertThat("Credit card expiration month should match", creditCard.expirationMonth, equalTo(ccExpMonth))
assertThat("Credit card expiration year should match", creditCard.expirationYear, equalTo(ccExpYearNew))
saveHandled.complete(null)
}
})
sessionRule.delegateDuringNextWait(object : PromptDelegate {
@AssertCalled
override fun onCreditCardSave(
session: GeckoSession,
request: AutocompleteRequest<CreditCardSaveOption>,
): GeckoResult<PromptDelegate.PromptResponse> {
assertThat("Session should not be null", session, notNullValue())
val option = request.options[0]
val cc = option.value
assertThat("Credit card should not be null", cc, notNullValue())
assertThat(
"Credit card name should match",
cc.name,
equalTo(ccName),
)
assertThat(
"Credit card number should match",
cc.number,
equalTo(ccNumber),
)
assertThat(
"Credit card expiration month should match",
cc.expirationMonth,
equalTo(ccExpMonth),
)
assertThat(
"Credit card expiration year should match",
cc.expirationYear,
equalTo(ccExpYear),
)
val modifiedCreditCard = CreditCard.Builder()
.name(cc.name)
.number(cc.number)
.expirationMonth(cc.expirationMonth)
.expirationYear(ccExpYearNew)
.build()
return GeckoResult.fromValue(request.confirm(CreditCardSaveOption(modifiedCreditCard)))
}
})
// Enter the card values
mainSession.evaluateJS("document.querySelector('#name').value = '$ccName'")
mainSession.evaluateJS("document.querySelector('#name').focus()")
mainSession.evaluateJS("document.querySelector('#number').value = '$ccNumber'")
mainSession.evaluateJS("document.querySelector('#number').focus()")
mainSession.evaluateJS("document.querySelector('#expMonth').value = '$ccExpMonth'")
mainSession.evaluateJS("document.querySelector('#expMonth').focus()")
mainSession.evaluateJS("document.querySelector('#expYear').value = '$ccExpYear'")
mainSession.evaluateJS("document.querySelector('#expYear').focus()")
// Submit the form
mainSession.evaluateJS("document.querySelector('form').requestSubmit()")
sessionRule.waitForResult(saveHandled)
}
@Test
fun creditCardUpdateAccept() {
val ccName = "MyCard"
val ccNumber1 = "5105105105105100"
val ccExpMonth1 = "6"
val ccExpYear1 = "2024"
val ccNumber2 = "4111111111111111"
val ccExpMonth2 = "11"
val ccExpYear2 = "2021"
val savedCreditCards = mutableListOf<CreditCard>()
mainSession.loadTestPath(CC_FORM_HTML_PATH)
mainSession.waitForPageStop()
val saveHandled1 = GeckoResult<Void>()
val saveHandled2 = GeckoResult<Void>()
sessionRule.delegateUntilTestEnd(object : StorageDelegate {
@AssertCalled
override fun onCreditCardFetch(): GeckoResult<Array<CreditCard>> {
return GeckoResult.fromValue(savedCreditCards.toTypedArray())
}
@AssertCalled(count = 2)
override fun onCreditCardSave(creditCard: CreditCard) {
assertThat(
"Credit card name should match",
creditCard.name,
equalTo(ccName),
)
assertThat(
"Credit card number should match",
creditCard.number,
equalTo(forEachCall(ccNumber1, ccNumber2)),
)
assertThat(
"Credit card expiration month should match",
creditCard.expirationMonth,
equalTo(forEachCall(ccExpMonth1, ccExpMonth2)),
)
assertThat(
"Credit card expiration year should match",
creditCard.expirationYear,
equalTo(forEachCall(ccExpYear1, ccExpYear2)),
)
val savedCC = CreditCard.Builder()
.guid("test1")
.name(creditCard.name)
.number(creditCard.number)
.expirationMonth(creditCard.expirationMonth)
.expirationYear(creditCard.expirationYear)
.build()
savedCreditCards.add(savedCC)
if (sessionRule.currentCall.counter == 1) {
saveHandled1.complete(null)
} else if (sessionRule.currentCall.counter == 2) {
saveHandled2.complete(null)
}
}
})
sessionRule.delegateUntilTestEnd(object : PromptDelegate {
@AssertCalled(count = 2)
override fun onCreditCardSave(
session: GeckoSession,
request: AutocompleteRequest<CreditCardSaveOption>,
): GeckoResult<PromptDelegate.PromptResponse> {
assertThat("Session should not be null", session, notNullValue())
val option = request.options[0]
val cc = option.value
assertThat("Credit card should not be null", cc, notNullValue())
assertThat(
"Credit card name should match",
cc.name,
equalTo(ccName),
)
assertThat(
"Credit card number should match",
cc.number,
equalTo(forEachCall(ccNumber1, ccNumber2)),
)
assertThat(
"Credit card expiration month should match",
cc.expirationMonth,
equalTo(forEachCall(ccExpMonth1, ccExpMonth2)),
)
assertThat(
"Credit card expiration year should match",
cc.expirationYear,
equalTo(forEachCall(ccExpYear1, ccExpYear2)),
)
return GeckoResult.fromValue(request.confirm(option))
}
})
// Enter the card values
mainSession.evaluateJS("document.querySelector('#name').value = '$ccName'")
mainSession.evaluateJS("document.querySelector('#name').focus()")
mainSession.evaluateJS("document.querySelector('#number').value = '$ccNumber1'")
mainSession.evaluateJS("document.querySelector('#number').focus()")
mainSession.evaluateJS("document.querySelector('#expMonth').value = '$ccExpMonth1'")
mainSession.evaluateJS("document.querySelector('#expMonth').focus()")
mainSession.evaluateJS("document.querySelector('#expYear').value = '$ccExpYear1'")
mainSession.evaluateJS("document.querySelector('#expYear').focus()")
// Submit the form
mainSession.evaluateJS("document.querySelector('form').requestSubmit()")
sessionRule.waitForResult(saveHandled1)
// Update credit card
val session2 = sessionRule.createOpenSession()
session2.loadTestPath(CC_FORM_HTML_PATH)
session2.waitForPageStop()
session2.evaluateJS("document.querySelector('#name').value = '$ccName'")
session2.evaluateJS("document.querySelector('#name').focus()")
session2.evaluateJS("document.querySelector('#number').value = '$ccNumber2'")
session2.evaluateJS("document.querySelector('#number').focus()")
session2.evaluateJS("document.querySelector('#expMonth').value = '$ccExpMonth2'")
session2.evaluateJS("document.querySelector('#expMonth').focus()")
session2.evaluateJS("document.querySelector('#expYear').value = '$ccExpYear2'")
session2.evaluateJS("document.querySelector('#expYear').focus()")
session2.evaluateJS("document.querySelector('form').requestSubmit()")
sessionRule.waitForResult(saveHandled2)
}
fun testLoginUsed(autofillEnabled: Boolean) {
sessionRule.setPrefsUntilTestEnd(
mapOf(
// Enable login management since it's disabled in automation.
"signon.rememberSignons" to true,
"signon.autofillForms.http" to true,
"signon.userInputRequiredToCapture.enabled" to false,
),
)
val usedHandled = GeckoResult<Void>()
val user1 = "user1x"
val pass1 = "pass1x"
val guid = "test-guid"
val origin = GeckoSessionTestRule.TEST_ENDPOINT
val savedLogin = LoginEntry.Builder()
.guid(guid)
.origin(origin)
.formActionOrigin(origin)
.username(user1)
.password(pass1)
.build()
val savedLogins = mutableListOf<LoginEntry>(savedLogin)
if (autofillEnabled) {
sessionRule.delegateUntilTestEnd(object : StorageDelegate {
@AssertCalled
override fun onLoginFetch(domain: String): GeckoResult<Array<LoginEntry>>? {
assertThat("Domain should match", domain, equalTo("localhost"))
return GeckoResult.fromValue(savedLogins.toTypedArray())
}
@AssertCalled(count = 1)
override fun onLoginUsed(login: LoginEntry, usedFields: Int) {
assertThat(
"Used fields should match",
usedFields,
equalTo(UsedField.PASSWORD),
)
assertThat(
"Username should match",
login.username,
equalTo(user1),
)
assertThat(
"Password should match",
login.password,
equalTo(pass1),
)
assertThat(
"GUID should match",
login.guid,
equalTo(guid),
)
usedHandled.complete(null)
}
})
} else {
sessionRule.delegateUntilTestEnd(object : StorageDelegate {
@AssertCalled
override fun onLoginFetch(domain: String): GeckoResult<Array<LoginEntry>>? {
assertThat("Domain should match", domain, equalTo("localhost"))
return GeckoResult.fromValue(savedLogins.toTypedArray())
}
@AssertCalled(false)
override fun onLoginUsed(login: LoginEntry, usedFields: Int) {}
})
}
sessionRule.delegateUntilTestEnd(object : PromptDelegate {
@AssertCalled(false)
override fun onLoginSave(
session: GeckoSession,
prompt: AutocompleteRequest<LoginSaveOption>,
): GeckoResult<PromptDelegate.PromptResponse>? {
return null
}
})
mainSession.loadTestPath(FORMS3_HTML_PATH)
mainSession.waitForPageStop()
mainSession.evaluateJS("document.querySelector('#form1').submit()")
if (autofillEnabled) {
sessionRule.waitForResult(usedHandled)
} else {
mainSession.waitForPageStop()
}
}
@Test
fun loginUsed() {
testLoginUsed(true)
}
@Test
fun loginAutofillDisabled() {
sessionRule.runtime.settings.loginAutofillEnabled = false
testLoginUsed(false)
sessionRule.runtime.settings.loginAutofillEnabled = true
}
fun testPasswordAutofill(autofillEnabled: Boolean) {
sessionRule.setPrefsUntilTestEnd(
mapOf(
// Enable login management since it's disabled in automation.
"signon.rememberSignons" to true,
"signon.autofillForms.http" to true,
"signon.userInputRequiredToCapture.enabled" to false,
),
)
val user1 = "user1x"
val pass1 = "pass1x"
val guid = "test-guid"
val origin = GeckoSessionTestRule.TEST_ENDPOINT
val savedLogin = LoginEntry.Builder()
.guid(guid)
.origin(origin)
.formActionOrigin(origin)
.username(user1)
.password(pass1)
.build()
val savedLogins = mutableListOf<LoginEntry>(savedLogin)
sessionRule.delegateUntilTestEnd(object : StorageDelegate {
@AssertCalled
override fun onLoginFetch(domain: String): GeckoResult<Array<LoginEntry>>? {
assertThat("Domain should match", domain, equalTo("localhost"))
return GeckoResult.fromValue(savedLogins.toTypedArray())
}
@AssertCalled(false)
override fun onLoginUsed(login: LoginEntry, usedFields: Int) {}
})
sessionRule.delegateUntilTestEnd(object : PromptDelegate {
@AssertCalled(false)
override fun onLoginSave(
session: GeckoSession,
prompt: AutocompleteRequest<LoginSaveOption>,
): GeckoResult<PromptDelegate.PromptResponse>? {
return null
}
})
mainSession.loadTestPath(FORMS3_HTML_PATH)
mainSession.waitForPageStop()
mainSession.evaluateJS("document.querySelector('#user1').focus()")
mainSession.evaluateJS(
"document.querySelector('#user1').value = '$user1'",
)
mainSession.pressKey(KeyEvent.KEYCODE_TAB)
val pass = mainSession.evaluateJS(
"document.querySelector('#pass1').value",
) as String
if (autofillEnabled) {
assertThat(
"Password should match",
pass,
equalTo(pass1),
)
} else {
assertThat(
"Password should not be filled",
pass,
equalTo(""),
)
}
}
@Test
fun loginAutofillDisabledPasswordAutofill() {
sessionRule.runtime.settings.loginAutofillEnabled = false
testPasswordAutofill(false)
sessionRule.runtime.settings.loginAutofillEnabled = true
}
@Test
fun loginAutofillEnabledPasswordAutofill() {
testPasswordAutofill(true)
}
@Test
fun loginSelectAccept() {
sessionRule.setPrefsUntilTestEnd(
mapOf(
// Enable login management since it's disabled in automation.
"signon.rememberSignons" to true,
"signon.autofillForms.http" to true,
"dom.disable_open_during_load" to false,
"signon.userInputRequiredToCapture.enabled" to false,
),
)
// Test:
// 1. Load a login form page.
// 2. Input un/pw and submit.
// a. Ensure onLoginSave is called accordingly.
// b. Save the submitted login entry.
// 3. Reload the login form page.
// a. Ensure onLoginFetch is called.
// b. Return empty login entry list to avoid autofilling.
// 4. Input a new set of un/pw and submit.
// a. Ensure onLoginSave is called again.
// b. Save the submitted login entry.
// 5. Reload the login form page.
// 6. Focus on the username input field.
// a. Ensure onLoginFetch is called.
// b. Return the saved login entries.
// c. Ensure onLoginSelect is called.
// d. Select and return one of the options.
// e. Submit the form.
// f. Ensure that onLoginUsed is called.
val user1 = "user1x"
val user2 = "user2x"
val pass1 = "pass1x"
val pass2 = "pass2x"
val savedLogins = mutableListOf<LoginEntry>()
val saveHandled1 = GeckoResult<Void>()
val saveHandled2 = GeckoResult<Void>()
val selectHandled = GeckoResult<Void>()
val usedHandled = GeckoResult<Void>()
sessionRule.delegateUntilTestEnd(object : StorageDelegate {
@AssertCalled
override fun onLoginFetch(domain: String): GeckoResult<Array<LoginEntry>>? {
assertThat("Domain should match", domain, equalTo("localhost"))
var logins = mutableListOf<LoginEntry>()
if (savedLogins.size == 2) {
logins = savedLogins
}
return GeckoResult.fromValue(logins.toTypedArray())
}
@AssertCalled(count = 2)
override fun onLoginSave(login: LoginEntry) {
var username = ""
var password = ""
var handle = GeckoResult<Void>()
if (sessionRule.currentCall.counter == 1) {
username = user1
password = pass1
handle = saveHandled1
} else if (sessionRule.currentCall.counter == 2) {
username = user2
password = pass2
handle = saveHandled2
}
val savedLogin = LoginEntry.Builder()
.guid(login.username)
.origin(login.origin)
.formActionOrigin(login.formActionOrigin)
.username(login.username)
.password(login.password)
.build()
savedLogins.add(savedLogin)
assertThat(
"Username should match",
login.username,
equalTo(username),
)
assertThat(
"Password should match",
login.password,
equalTo(password),
)
handle.complete(null)
}
@AssertCalled(count = 1)
override fun onLoginUsed(login: LoginEntry, usedFields: Int) {
assertThat(
"Used fields should match",
usedFields,
equalTo(UsedField.PASSWORD),
)
assertThat(
"Username should match",
login.username,
equalTo(user1),
)
assertThat(
"Password should match",
login.password,
equalTo(pass1),
)
assertThat(
"GUID should match",
login.guid,
equalTo(user1),
)
usedHandled.complete(null)
}
})
mainSession.loadTestPath(FORMS3_HTML_PATH)
mainSession.waitForPageStop()
mainSession.delegateDuringNextWait(object : PromptDelegate {
@AssertCalled(count = 1)
override fun onLoginSave(
session: GeckoSession,
prompt: AutocompleteRequest<LoginSaveOption>,
): GeckoResult<PromptDelegate.PromptResponse>? {
assertThat("Session should not be null", session, notNullValue())
val option = prompt.options[0]
val login = option.value
assertThat("Login should not be null", login, notNullValue())
assertThat(
"Username should match",
login.username,
equalTo(user1),
)
assertThat(
"Password should match",
login.password,
equalTo(pass1),
)
return GeckoResult.fromValue(prompt.confirm(option))
}
})
// Assign login credentials.
mainSession.evaluateJS("document.querySelector('#user1').value = '$user1'")
mainSession.evaluateJS("document.querySelector('#pass1').value = '$pass1'")
// Submit the form.
mainSession.evaluateJS("document.querySelector('#form1').submit()")
sessionRule.waitForResult(saveHandled1)
// Reload.
val session2 = sessionRule.createOpenSession()
session2.loadTestPath(FORMS3_HTML_PATH)
session2.waitForPageStop()
session2.delegateDuringNextWait(object : PromptDelegate {
@AssertCalled(count = 1)
override fun onLoginSave(
session: GeckoSession,
prompt: AutocompleteRequest<LoginSaveOption>,
): GeckoResult<PromptDelegate.PromptResponse>? {
assertThat("Session should not be null", session, notNullValue())
val option = prompt.options[0]
val login = option.value
assertThat("Login should not be null", login, notNullValue())
assertThat(
"Username should match",
login.username,
equalTo(user2),
)
assertThat(
"Password should match",
login.password,
equalTo(pass2),
)
return GeckoResult.fromValue(prompt.confirm(option))
}
})
// Assign alternative login credentials.
session2.evaluateJS("document.querySelector('#user1').value = '$user2'")
session2.evaluateJS("document.querySelector('#pass1').value = '$pass2'")
// Submit the form.
session2.evaluateJS("document.querySelector('#form1').submit()")
sessionRule.waitForResult(saveHandled2)
// Reload for the last time.
val session3 = sessionRule.createOpenSession()
session3.delegateUntilTestEnd(object : PromptDelegate {
@AssertCalled(count = 1)
override fun onLoginSelect(
session: GeckoSession,
prompt: AutocompleteRequest<LoginSelectOption>,
): GeckoResult<PromptDelegate.PromptResponse>? {
assertThat("Session should not be null", session, notNullValue())
assertThat(
"There should be two options",
prompt.options.size,
equalTo(2),
)
var usernames = arrayOf(user1, user2)
var passwords = arrayOf(pass1, pass2)
for (i in 0..1) {
val login = prompt.options[i].value
assertThat("Login should not be null", login, notNullValue())
assertThat(
"Username should match",
login.username,
equalTo(usernames[i]),
)
assertThat(
"Password should match",
login.password,
equalTo(passwords[i]),
)
}
Handler(Looper.getMainLooper()).postDelayed({
selectHandled.complete(null)
}, acceptDelay)
return GeckoResult.fromValue(prompt.confirm(prompt.options[0]))
}
})
session3.loadTestPath(FORMS3_HTML_PATH)
session3.waitForPageStop()
// Focus on the username input field.
session3.evaluateJS("document.querySelector('#user1').focus()")
sessionRule.waitForResult(selectHandled)
assertThat(
"Filled username should match",
session3.evaluateJS("document.querySelector('#user1').value") as String,
equalTo(user1),
)
assertThat(
"Filled password should match",
session3.evaluateJS("document.querySelector('#pass1').value") as String,
equalTo(pass1),
)
// Submit the selection.
session3.evaluateJS("document.querySelector('#form1').submit()")
sessionRule.waitForResult(usedHandled)
}
@Test
fun loginSelectModifyAccept() {
sessionRule.setPrefsUntilTestEnd(
mapOf(
// Enable login management since it's disabled in automation.
"signon.rememberSignons" to true,
"signon.autofillForms.http" to true,
"dom.disable_open_during_load" to false,
"signon.userInputRequiredToCapture.enabled" to false,
),
)
// Test:
// 1. Load a login form page.
// 2. Input un/pw and submit.
// a. Ensure onLoginSave is called accordingly.
// b. Save the submitted login entry.
// 3. Reload the login form page.
// a. Ensure onLoginFetch is called.
// b. Return empty login entry list to avoid autofilling.
// 4. Input a new set of un/pw and submit.
// a. Ensure onLoginSave is called again.
// b. Save the submitted login entry.
// 5. Reload the login form page.
// 6. Focus on the username input field.
// a. Ensure onLoginFetch is called.
// b. Return the saved login entries.
// c. Ensure onLoginSelect is called.
// d. Select and return a new login entry.
// e. Submit the form.
// f. Ensure that onLoginUsed is not called.
val user1 = "user1x"
val user2 = "user2x"
val pass1 = "pass1x"
val pass2 = "pass2x"
val userMod = "user1xmod"
val passMod = "pass1xmod"
val savedLogins = mutableListOf<LoginEntry>()
val saveHandled1 = GeckoResult<Void>()
val saveHandled2 = GeckoResult<Void>()
val selectHandled = GeckoResult<Void>()
sessionRule.delegateUntilTestEnd(object : StorageDelegate {
@AssertCalled
override fun onLoginFetch(domain: String): GeckoResult<Array<LoginEntry>>? {
assertThat("Domain should match", domain, equalTo("localhost"))
var logins = mutableListOf<LoginEntry>()
if (savedLogins.size == 2) {
logins = savedLogins
}
return GeckoResult.fromValue(logins.toTypedArray())
}
@AssertCalled(count = 2)
override fun onLoginSave(login: LoginEntry) {
var username = ""
var password = ""
var handle = GeckoResult<Void>()
if (sessionRule.currentCall.counter == 1) {
username = user1
password = pass1
handle = saveHandled1
} else if (sessionRule.currentCall.counter == 2) {
username = user2
password = pass2
handle = saveHandled2
}
val savedLogin = LoginEntry.Builder()
.guid(login.username)
.origin(login.origin)
.formActionOrigin(login.formActionOrigin)
.username(login.username)
.password(login.password)
.build()
savedLogins.add(savedLogin)
assertThat(
"Username should match",
login.username,
equalTo(username),
)
assertThat(
"Password should match",
login.password,
equalTo(password),
)
handle.complete(null)
}
@AssertCalled(false)
override fun onLoginUsed(login: LoginEntry, usedFields: Int) {}
})
mainSession.loadTestPath(FORMS3_HTML_PATH)
mainSession.waitForPageStop()
mainSession.delegateDuringNextWait(object : PromptDelegate {
@AssertCalled(count = 1)
override fun onLoginSave(
session: GeckoSession,
prompt: AutocompleteRequest<LoginSaveOption>,
): GeckoResult<PromptDelegate.PromptResponse>? {
assertThat("Session should not be null", session, notNullValue())
val option = prompt.options[0]
val login = option.value
assertThat("Login should not be null", login, notNullValue())
assertThat(
"Username should match",
login.username,
equalTo(user1),
)
assertThat(
"Password should match",
login.password,
equalTo(pass1),
)
return GeckoResult.fromValue(prompt.confirm(option))
}
})
// Assign login credentials.
mainSession.evaluateJS("document.querySelector('#user1').value = '$user1'")
mainSession.evaluateJS("document.querySelector('#pass1').value = '$pass1'")
// Submit the form.
mainSession.evaluateJS("document.querySelector('#form1').submit()")
sessionRule.waitForResult(saveHandled1)
// Reload.
val session2 = sessionRule.createOpenSession()
session2.loadTestPath(FORMS3_HTML_PATH)
session2.waitForPageStop()
session2.delegateDuringNextWait(object : PromptDelegate {
@AssertCalled(count = 1)
override fun onLoginSave(
session: GeckoSession,
prompt: AutocompleteRequest<LoginSaveOption>,
): GeckoResult<PromptDelegate.PromptResponse>? {
assertThat("Session should not be null", session, notNullValue())
val option = prompt.options[0]
val login = option.value
assertThat("Login should not be null", login, notNullValue())
assertThat(
"Username should match",
login.username,
equalTo(user2),
)
assertThat(
"Password should match",
login.password,
equalTo(pass2),
)
return GeckoResult.fromValue(prompt.confirm(option))
}
})
// Assign alternative login credentials.
session2.evaluateJS("document.querySelector('#user1').value = '$user2'")
session2.evaluateJS("document.querySelector('#pass1').value = '$pass2'")
// Submit the form.
session2.evaluateJS("document.querySelector('#form1').submit()")
sessionRule.waitForResult(saveHandled2)
// Reload for the last time.
val session3 = sessionRule.createOpenSession()
session3.delegateUntilTestEnd(object : PromptDelegate {
@AssertCalled(count = 1)
override fun onLoginSelect(
session: GeckoSession,
prompt: AutocompleteRequest<LoginSelectOption>,
): GeckoResult<PromptDelegate.PromptResponse>? {
assertThat("Session should not be null", session, notNullValue())
assertThat(
"There should be two options",
prompt.options.size,
equalTo(2),
)
var usernames = arrayOf(user1, user2)
var passwords = arrayOf(pass1, pass2)
for (i in 0..1) {
val login = prompt.options[i].value
assertThat("Login should not be null", login, notNullValue())
assertThat(
"Username should match",
login.username,
equalTo(usernames[i]),
)
assertThat(
"Password should match",
login.password,
equalTo(passwords[i]),
)
}
val login = prompt.options[0].value
val modOption = LoginSelectOption(
LoginEntry.Builder()
.origin(login.origin)
.formActionOrigin(login.formActionOrigin)
.username(userMod)
.password(passMod)
.build(),
)
Handler(Looper.getMainLooper()).postDelayed({
selectHandled.complete(null)
}, acceptDelay)
return GeckoResult.fromValue(prompt.confirm(modOption))
}
})
session3.loadTestPath(FORMS3_HTML_PATH)
session3.waitForPageStop()
// Focus on the username input field.
session3.evaluateJS("document.querySelector('#user1').focus()")
sessionRule.waitForResult(selectHandled)
assertThat(
"Filled username should match",
session3.evaluateJS("document.querySelector('#user1').value") as String,
equalTo(userMod),
)
assertThat(
"Filled password should match",
session3.evaluateJS("document.querySelector('#pass1').value") as String,
equalTo(passMod),
)
// Submit the selection.
session3.evaluateJS("document.querySelector('#form1').submit()")
session3.waitForPageStop()
}
@Test
fun loginSelectGeneratedPassword() {
sessionRule.setPrefsUntilTestEnd(
mapOf(
// Enable login management since it's disabled in automation.
"signon.rememberSignons" to true,
"signon.autofillForms.http" to true,
"signon.generation.enabled" to true,
"signon.generation.available" to true,
"dom.disable_open_during_load" to false,
"signon.userInputRequiredToCapture.enabled" to false,
),
)
// Test:
// 1. Load a login form page.
// 2. Input username.
// 3. Focus on the password input field.
// a. Ensure onLoginSelect is called with a generated password.
// b. Return the login entry with the generated password.
// 4. Submit the login form.
// a. Ensure onLoginSave is called with accordingly.
val user1 = "user1x"
var genPass = ""
val saveHandled1 = GeckoResult<Void>()
val saveHandled2 = GeckoResult<Void>()
val selectHandled = GeckoResult<Void>()
var numSelects = 0
var onLoginSaveCount = 0
sessionRule.delegateUntilTestEnd(object : StorageDelegate {
@AssertCalled
override fun onLoginFetch(domain: String): GeckoResult<Array<LoginEntry>>? {
assertThat("Domain should match", domain, equalTo("localhost"))
return GeckoResult.fromValue(null)
}
@AssertCalled(count = 2)
override fun onLoginSave(login: LoginEntry) {
onLoginSaveCount++
assertThat(
"Password should match",
login.password,
equalTo(genPass),
)
if (onLoginSaveCount == 1) {
assertThat(
"Username should be empty",
login.username,
equalTo(""),
)
saveHandled1.complete(null)
} else if (onLoginSaveCount == 2) {
assertThat(
"Username should match",
login.username,
equalTo(user1),
)
saveHandled2.complete(null)
}
}
@AssertCalled(false)
override fun onLoginUsed(login: LoginEntry, usedFields: Int) {}
})
mainSession.loadTestPath(FORMS4_HTML_PATH)
mainSession.waitForPageStop()
mainSession.delegateUntilTestEnd(object : PromptDelegate {
@AssertCalled
override fun onLoginSelect(
session: GeckoSession,
prompt: AutocompleteRequest<LoginSelectOption>,
): GeckoResult<PromptDelegate.PromptResponse>? {
assertThat("Session should not be null", session, notNullValue())
assertThat(
"There should be one option",
prompt.options.size,
equalTo(1),
)
val option = prompt.options[0]
val login = option.value
assertThat(
"Hint should match",
option.hint,
equalTo(SelectOption.Hint.GENERATED),
)
assertThat("Login should not be null", login, notNullValue())
assertThat(
"Password should not be empty",
login.password,
not(isEmptyOrNullString()),
)
genPass = login.password
if (numSelects == 0) {
Handler(Looper.getMainLooper()).postDelayed({
selectHandled.complete(null)
}, acceptDelay)
}
++numSelects
return GeckoResult.fromValue(prompt.confirm(option))
}
@AssertCalled(count = 1)
override fun onLoginSave(
session: GeckoSession,
prompt: AutocompleteRequest<LoginSaveOption>,
): GeckoResult<PromptDelegate.PromptResponse>? {
assertThat("Session should not be null", session, notNullValue())
val option = prompt.options[0]
val login = option.value
assertThat("Login should not be null", login, notNullValue())
assertThat(
"Username should match",
login.username,
equalTo(user1),
)
// TODO: The flag is only set for login entry updates yet.
/*
assertThat(
"Hint should match",
option.hint,
equalTo(LoginSaveOption.Hint.GENERATED))
*/
assertThat(
"Password should not be empty",
login.password,
not(isEmptyOrNullString()),
)
assertThat(
"Password should match",
login.password,
equalTo(genPass),
)
return GeckoResult.fromValue(prompt.confirm(option))
}
})
// Assign username and focus on password.
mainSession.evaluateJS("document.querySelector('#user1').value = '$user1'")
mainSession.evaluateJS("document.querySelector('#pass1').focus()")
sessionRule.waitForResult(selectHandled)
assertThat(
"Filled username should match",
mainSession.evaluateJS("document.querySelector('#user1').value") as String,
equalTo(user1),
)
val filledPass = mainSession.evaluateJS(
"document.querySelector('#pass1').value",
) as String
assertThat(
"Password should not be empty",
filledPass,
not(isEmptyOrNullString()),
)
assertThat(
"Filled password should match",
filledPass,
equalTo(genPass),
)
val filledConfirmPass = mainSession.evaluateJS(
"document.querySelector('#passConfirm').value",
) as String
assertThat(
"Confirm password should match filled password",
filledConfirmPass,
equalTo(genPass),
)
sessionRule.waitForResult(saveHandled1)
// Submit the selection.
mainSession.evaluateJS("document.querySelector('#form1').submit()")
mainSession.waitForPageStop()
sessionRule.waitForResult(saveHandled2)
}
@Test
fun loginSelectDismiss() {
sessionRule.setPrefsUntilTestEnd(
mapOf(
// Enable login management since it's disabled in automation.
"signon.rememberSignons" to true,
"signon.autofillForms.http" to true,
"signon.userInputRequiredToCapture.enabled" to false,
),
)
val user = arrayOf("user1x", "user2x")
val pass = arrayOf("pass1x", "pass2x")
val guid = arrayOf("test-guid1", "test-guid2")
val origin = GeckoSessionTestRule.TEST_ENDPOINT
val savedLogins = arrayOf(
LoginEntry.Builder()
.guid(guid[0])
.origin(origin)
.formActionOrigin(origin)
.username(user[0])
.password(pass[0])
.build(),
LoginEntry.Builder()
.guid(guid[1])
.origin(origin)
.formActionOrigin(origin)
.username(user[1])
.password(pass[1])
.build(),
)
sessionRule.delegateUntilTestEnd(object : StorageDelegate {
@AssertCalled
override fun onLoginFetch(domain: String): GeckoResult<Array<LoginEntry>>? {
return GeckoResult.fromValue(savedLogins)
}
})
val result = GeckoResult<PromptDelegate.PromptResponse>()
val promptInstanceDelegate = object : PromptDelegate.PromptInstanceDelegate {
override fun onPromptDismiss(prompt: PromptDelegate.BasePrompt) {
result.complete(prompt.dismiss())
}
}
val promptHandled = GeckoResult<Void>()
mainSession.delegateUntilTestEnd(object : PromptDelegate {
@AssertCalled
override fun onLoginSelect(session: GeckoSession, prompt: AutocompleteRequest<LoginSelectOption>): GeckoResult<PromptDelegate.PromptResponse>? {
assertThat(
"There should be two options",
prompt.options.size,
equalTo(2),
)
prompt.setDelegate(promptInstanceDelegate)
Handler(Looper.getMainLooper()).postDelayed({
promptHandled.complete(null)
}, acceptDelay)
return GeckoResult()
}
})
mainSession.loadTestPath(FORMS3_HTML_PATH)
mainSession.waitForPageStop()
mainSession.evaluateJS("document.querySelector('#user1').focus()")
sessionRule.waitForResult(promptHandled)
mainSession.evaluateJS("document.querySelector('#user1').blur()")
sessionRule.waitForResult(result)
}
}