Set app as default button interaction with UI automator - android

I have a test case where in the app "Set as default" prompt is opened. I want to test that with UI automator, and I had success with testing that case, but not 100% reliable. Unfortunately, some devices have "Set as default" prompt button written in caps, and some of those don't, so I'm not able to create 100% reliable tests for this test case. I have written this code below, but when fetching "Set as default" button by text, case of the letters don't play a role, but when I want to interact with that button, text case is important. Switching the IF-ELSE cases doesn't fix the problem in this case. And somehow, none of the dialog buttons ids work (button1, button2..) when I want to press those.
if (roleManager.isRoleAvailable(android.app.role.ASSISTANT)) {
if (!roleManager.isRoleHeld(android.app.role.ASSISTANT)) {
val myApp = device.findObject(UiSelector().textMatches(InstrumentationRegistry.getInstrumentation().targetContext.getString(R.string.app_name)))
myApp.click()
sleepLong()
var setAsDefaultButton: UiObject? = null
if (device.findObject(UiSelector().text("Set as default")) != null) {
setAsDefaultButton = device.findObject(UiSelector().text("Set as default"))
setAsDefaultButton?.click()
} else if (device.findObject(UiSelector().text("SET AS DEFAULT")) != null) {
setAsDefaultButton = device.findObject(UiSelector().text("SET AS DEFAULT"))
setAsDefaultButton?.click()
} else {
clickDialogPositiveButton()
}
}
}

You can use the Pattern object instead of using a string.
You can use in your code like:
val pattern = Pattern.compile("Set as default", Pattern.CASE_INSENSITIVE)
val setDefaultText = device.findObject(UiSelector().text(pattern))
if(setDefaultText != null)) {
setDefaultText.click()
} else {
clickDialogPositiveButton()
}

Based on the Jordan's example and hint, solution to this is to find an object with the Pattern. With pattern, you can search for UIObject with By.text(pattern). Take a note that the object found with the pattern needs to be UIObject2 instead of the UIObject.
val pattern = Pattern.compile("Set as the default", Pattern.CASE_INSENSITIVE)
if(device.findObject(UiSelector().text(pattern.toString())) != null) {
device.findObject(By.text(pattern)).click()
}

Related

LiveMutableData and copies update to same value?

I have a MutableLiveData variable in my AppRepository which is updated and contains my data. This I have no issues with. I also have the following observable to trigger a UI update with the data it holds in my onCreateView function:
viewModel.projectWithContent.observe(viewLifecycleOwner, {
pwc = it
counterList = it.counterList
})
When I tap either to increase or decrease the counter count and then try to push the update to my Room database, it skips it. I have the following check currently:
if(counterList != null) {
try {
for(counter: Counter in counterList!!) {
if(counter.counter_count != pwc?.counterList!![
pwc?.counterList!!.indexOf(counter)
].counter_count) {
Log.i(LOG_TAG, "Hello")
} else {
Log.i(LOG_TAG, "Goodbye")
}
}
} catch(e: IndexOutOfBoundsException) {
e.printStackTrace()
}
}
It'll always go to Goodbye.
Now. If I put the following just below try
Log.i(LOG_TAG, "PWC: ${pwc?.counterList!![0].counter_count}, " +
"CPWC: ${counterList!![0].counter_count}," +
"VMPWC: ${viewModel.projectWithContent.value?.counterList!![0].counter_count}")
It provides the following output:
PWC: 70, CPWC: 70,VMPWC: 70
Is this a side effect of what I'm doing or?
Thanks
Like #Tenfour04 says, your condition is actually checking they don't match, so "Goodbye" is the output when they do match.
If you don't mind (this is a little long), I just want to recommend some stuff because I feel like you're making life hard for yourself with all the null-checking that's going on - the logic of the code was really hard to read, and I'm guessing that's why you didn't notice the flipped logic too!
First: the ? null safety stuff (and !! which is the opposite of safe, never use it unless you know you have good reason) is there because you have nullable variable types. Normally the IDE would smart cast them to non-null once you've done a null check (like on your first line) - but because they're vars, they can be changed at any time.
That means that a variable that wasn't null before could be now, so you're forced to null-check every single time you access it. But even if the types weren't nullable, because they're vars, they can still change, and the thing you were looking at a moment ago is something different now.
The simple solution is to just make a new variable:
val counters = counterList
if (counters != null) {
...
}
// or if you want to use one of kotlin's scope functions
counterList?.let { counters ->
...
}
Because that new one is a val, it's not going to change what it's pointing at! Once it's null-checked, it's always going to be non-null, so you don't need to use ? anymore.
You have a couple of variables to make - you want to make sure pwc isn't null, and also their counterLists. A quick way to do that is with pwc?.counterList - if pwc is null, it will return null. Otherwise it will move to the next step, and return counterList, which may be null. (Using !! is saying that it definitely never will be null, in which case it shouldn't be nullable at all!)
And you don't actually care about pwc anyway - you're just comparing its counterList to the other, so why don't we pare it back to just those?
val counters = counterList
val pwcCounters = pwc?.counterList
if (counters != null && pwcCounters != null) {
try {
for(counter: Counter in counters) {
if(counter.counter_count != pwcCounters[
pwcCounters.indexOf(counter)
].counter_count) {
Log.i(LOG_TAG, "Hello")
} else {
Log.i(LOG_TAG, "Goodbye")
}
}
} catch(e: IndexOutOfBoundsException) {
e.printStackTrace()
}
}
There's more we could do here, but just by cleaning up those nulls and using the specific variables we want to work with, does that feel easier to read? And more importantly, easier to understand what's happening and what could happen?
Might be worth throwing it in a function too, stops the call site getting cluttered with these temp variables:
fun doThing(counters: List<Counter>?, pwcCounters: List<Counter>?) {
if (counters == null || pwcCounters == null) return
// do the stuff
}
// when you want to do the thing:
doThing(counterList, pwc?.counterList)
So all your null checking is out of the way, your "temp variables" are the fixed parameters passed to the function, it's all nice and neat.
I know this is a long post for such a short bit of code, but it's a good habit to get into - if you're writing code where you're working with nullable vars and you're wrestling with the null safety system, or you keep repeating yourself to access a particular variable nested inside another object, you can make things a lot easier for yourself! You can imagine how wild this could all get for more complex code.
Also if you care, this is how I'd personally write it, if it helps!
fun doThing(counters: List<Counter>?, pwcCounters: List<Counter>?) {
if (counters == null || pwcCounters == null) return
// for (counter in Counters) is fine too I just like this version
counters.forEach { counter ->
// find returns the first item that matches the condition, or null if nothing matches,
// so no need to handle any exceptions, just handle the potential null!
// (this is a really common Kotlin pattern, lots of functions have a "returns null on failure" version)
val pwcCounter = pwcCounters.find { it == counter }
// remember pwcCounter can be null, so we have to use ? to access its count safely.
// If it evaluates to null, the match just fails
if (counter.count == pwcCounter?.count) Log.i(LOG_TAG, "Hello")
else Log.i(LOG_TAG, "Goodbye")
}
}
I also renamed counter_count to just count since it's a property on a Counter anyway. I feel like counter.count is easier to read than counter.counter_count, y'know? It's the little things

Android Mockito Test on Activity's method which shows alert dialog

I am new to the Mockito Android Test framework. I have written one method in the activity and which shows the AlertDialog and I am writing test cases for that method.
Here are the functions of the activity which shows the alert dialog box:
fun showDialogBox(
shortMessage: String?,
longMessage: String?,
progress: Int,
status: String?
) {
runOnUiThread {
if (cdfwExtractionStatusAlertDialog == null) {
val mDialogView = LayoutInflater.from(this).inflate(
R.layout.cdfw_extraction_status_layout,
null
)
val mBuilder = AlertDialog.Builder(this)
.setView(mDialogView)
.setCancelable(false)
shortMessageTv = mDialogView.findViewById(R.id.short_message_tv)
longMessageTv = mDialogView.findViewById(R.id.long_message_tv)
closeButton = mDialogView.findViewById(R.id.close_button)
cdfwProcessBar = mDialogView.findViewById(R.id.progress_bar)
cdfwProcessBar.progress = progress
shortMessageTv.text = shortMessage
longMessageTv.text = longMessage
closeButton.setOnClickListener {
cdfwExtractionStatusAlertDialog?.dismiss()
}
checkStatus(status)
cdfwExtractionStatusAlertDialog = mBuilder.show()
} else {
shortMessageTv.text = longMessage
longMessageTv.text = longMessage
cdfwProcessBar.progress = progress
checkStatus(status)
}
}
}
Here are my mockito test cases for the same function:
#Test
public void testCDFWExtractionStatusNoZipFound(){
MainActivity activity = Mockito.mock(MainActivity.class);
activity.showDialogBox("","CDFW Required.",0,"FAILED_NO_ZIP_FOUND");
Mockito.matches("CDFW Extraction Status");
Mockito.matches("CDFW Required.");
Mockito.matches("Close");
}
#Test
public void testCDFWExtractionStatusInProgress(){
MainActivity activity = Mockito.mock(MainActivity.class);
activity.showDialogBox("","CDFW extraction ongoing",0,"IN_PROGRESS");
Mockito.matches("CDFW Extraction Status");
Mockito.matches("CDFW extraction ongoing");
}
It shows the test passed successfully but I am not sure whether this is the right way to write test cases for the same function if this is the wrong method then how do I write a test case for the same function.
When you are testing your UI it is better to use instrumented testing.
Instrumentation testing is testing that runs on Physical devices, so, in your case, the test will run in a real device or emulator to check if the flow is right.
For that, you can use Espresso, which is a framework provided by AndroidX.
For more detail, please check this out:
Test UI for a single app
Now, why not use Mockito for UI testing?
When you are testing the UI, you have to ensure the right flows to the user and good interaction. You can mock the scenario, but you can't test the whole flow because you don't know how it will behave when, for example, a click is made in a different place than the one you are testing on.
If you want to test for example that your Alert Dialog is shown, you can use:
#Test
fun testCDFWExtractionStatusNoZipFound() {
//..
onView(withId(R.id.dialogId)).check(matches(allOf(withText("CDFW Required"), isDisplayed()));
//..
}

Android How can I detect autofill pressed?

I use AutofillManager https://developer.android.com/reference/android/view/autofill/AutofillManager and can't understand how can I detect is user enter value himself or user select any autofill option.
I try to use editText.autofillValue but it contains value both cases
Anyone knows how can I resolve it? Help me, please!)
P.S. code
I have function to request autofill
fun allowAutoFill(view: View?) {
if (view != null) {
val afm = requireContext().getSystemService(AutofillManager::class.java)
if (afm.isAutofillSupported && afm.isEnabled) {
afm?.requestAutofill(view)
}
}
}
After that i want to know user enter something or select value from autofill. It's needed for analytics.
private fun isAutoFillApplied() = binding?.editPassword?.autofillValue?.textValue?.isNotEmpty() == true
but binding?.editPassword?.autofillValue contains value if user enter something and if user select autofill option
I couldn't find cool answer, so I used bad dirty hack.
I have two edittext in screen. If user select any autofill option this edittexts filled the same time. So if time difference on text changed not so big, I fill isAutofill value as true.
This is really not pretty solution, but I can't find another.
This is looks like this
var lastChangedTime = 0L
var isAutoFill = false
var lastRepeatPassword: String? = null
editPassword.addTextChangedListener { text ->
lastChangedTime = System.currentTimeMillis()
}
editRepeatPassword.addTextChangedListener { text ->
if (text.toString() != lastRepeatPassword) {
lastRepeatPassword = text.toString()
isAutoFill = (System.currentTimeMillis() - lastChangedTime) < 50
}
}

kotlin: How to get if EditText has setError enabled?

How can I validate if EditText has setError enabled ?
I want to disable a button if EditText has an error.
Any other way to achieve this.
It kinda works when I put view.calcbutton.setEnabled(false) inside the validateEditText-function, but I use the validateEditText-function to validate multiple EditTexts and only the last function-call disables the button.
if the first function-call disables the button, the second enables it again, and vice versa.
But I want do it outside this function because if one of the multiple EditTexts has setError the button should be disabled.
//global var blockcalcbutton
var blockcalcbutton = 0
//function to validate EditTexts and set blockcalcbutton=1 if setError
validateEditText(view.input_volt, view, getString(R.string.invalid_volt))
if(blockcalcbutton == 1) {
view.calcbutton.setEnabled(false)
view.calcbutton.setText(getString(R.string.calcbutton_disabled))
view.calcbutton.setBackgroundResource(R.color.buttonDisabled)
} else {
view.calcbutton.setEnabled(true)
view.calcbutton.setText(getString(R.string.calcbutton_enabled))
view.calcbutton.setBackgroundResource(R.color.buttonBackground)
}
fun validateEditText(editText: EditText, message: String) {
val myEditText = editText
myEditText.addTextChangedListener(object: TextWatcher {
override fun afterTextChanged(s: Editable?) {
if(myEditText.text.toString() == "" || myEditText.text.toString() == "." || myEditText.text.toString() == "0") {
//setError
myEditText.setError(message)
//var to disable Button
blockcalcbutton = 1
} else {
//delete setError
myEditText.setError(null)
//var to enable Button
blockcalcbutton = 0
}
}
You can create a callback to notify when you set an error or delete it.
interface EditTextErrorListener {
fun onErrorSet()
fun onErrorDeleted()
}
Here you can notify:
if(myEditText.text.toString() == "" || myEditText.text.toString() == "." || myEditText.text.toString() == "0") {
//setError
myEditText.setError(message)
---> listener.onErrorSet()
//var to disable Button
blockcalcbutton = 1
} else {
//delete setError
myEditText.setError(null)
---> listener.onErrorDeleted()
//var to enable Button
blockcalcbutton = 0
}
Try approaching the problem from further away; when you look at this issue, you have multiple inputs (all the fields in your form) and one boolean output:
All fields are OK -> Enable the button
One or more fields are NOT Ok -> disable the button.
Additionally, you have local validation on each field (to display the error, etc.).
I'd argue that the local validation on each field, is to be done at the callback from the edit text (onAfterText, etc.etc.). You are already doing this.
A way to ensure the final validation (of the form as a whole) is fast, you could use a reference counter. E.g.:
Each edit text, validates with afterTextChanged. Each one performs whatever validation you think is right (can be a shared one if they are all the same).
If validation fails, you keep a reference to the failed field.
This will not have side-effects because nothing happens whether the item is or is not on the list.
This is some pseudo-code:
// keep a list of fields (this is just a way to do it, there are many others)
var errorFields = MutableHashSet<EditText>
later in your "validation" (afterTextChanges for example):
if (xxx && yyy && zzz) { //your validations for the individual editText
//setError
myEditText.setError(message)
// Store the reference of the editField in error.
errorFields.add(theEditTextThatHasAFailure).
} else {
myEditText.setError(null)
// If the validation is ok, you remove it:
errorFields.remove(theEditTextThatHasFailure)
}
// The form may have changed, update the global button state.
updateButtonState();
All this method needs to do, is something like:
button.enabled = errorFields.isEmpty()
This will only be empty if there are no error fields.
This is just an idea you may need to combine with callbacks for further control, but remember this one thing:
EditTexts (or any other widget) is and should not be responsible for the business logic that drives the whole "Form"; they are merely individual pieces of a larger puzzle, and as such, it's incorrect to give them the responsibility to drive your Form's validations; they can (and should) however, validate themselves and handle their own error state (like you're doing), but that's as far as it should go.
They can inform of a state change (e.g. via the listener onAfterText, or after gaining/losing focus, etc.) but shouldn't make business logic decisions. EditTexts are designed to take user input and display it on screen, that's all.
Last but not least, don't forget to remove the references when you destroy your views
onDestroy() {
errorFields.clear()
}

Show Dialog Box when Button is clicked and the String of EditText is empty

I want to make that when the user click the button below and the String on EditText is empty, it shows a dialog.
So I made this method, but unfortunately rather than showing a Dialog Box, the app crashed. There's no problem with the Dialog box method, the problem is the IF function doesn't read what I requested properly.
Anybody has the solution for this?
Here's my method:
public void onClick(View v) {
if(v == launchSimplePayment) {
String amount = paymentAmount.getText().toString();
System.out.println(amount);
if (amount == "")
{
errorDialog();
}
else
{
System.out.println(amount);
// Use our helper function to create the simple payment.
PayPalPayment payment = exampleSimplePayment();
// Use checkout to create our Intent.
Intent checkoutIntent = PayPal.getInstance().checkout(payment, this, new ResultDelegate());
// Use the android's startActivityForResult() and pass in our Intent. This will start the library.
startActivityForResult(checkoutIntent, request);
}
}
Change this
if (amount == "")
with this
if (amount.equals(""))
Remember that the operator == compares references, not the content!
You should rewrite the condition like this:
if (amount.trim().equals(""))
Since a blank space like this " " would pass over your validation check.

Categories

Resources