I am trying to check if element exist, but appium seems to ignore searching an element when we specified that it should be inside another element. For example:
driver.findElementByAccessibilityId("First element").findElementByAccessibilityId("Second element");
It should work somehow since there is option in Appium inspector called Locator where after selecting strategy and choosing search from selected element option it finds what I expect.
What is the proper way of finding such elements ?
You can use a xpath for the same. Sample xpath for the situation will be like :
driver.findElement(By.xpath("//*[#accessibility-id='FirstElement']//*[#accessibility-id='FirstElement']"))
The // between first element and second element indicates first element is the base element for start searching the second element. So it will search for the second element between the scope of the first element
Simple solution but not the cleanest below:
I am using TestNG framework which I suppose do not allow declarations such as
private WebElement element= driver.findElementByAccessibilityId("First element").findElementByAccessibilityId("Second element");
Simple solution but not the best is to declare only parents as:
#FindBy(name="Parent")
private WebElement ParentElement;
Then in test case for example:
ParentElement.findElement(By.name("childElement")).isDisplayed())
Put the first element as parent and second element as child -
MobileElement parentButtonList = (MobileElement)driver.findElementById(parentClassIdentifier);
List<MobileElement> childButtonList = parentButtonList.findElementsById(childClassIdentifier);
Related
I'm writing an automated test for logging in to an Android app. I'm recording tests with Record Espresso Test and then edit the code as it's usually full of bugs.
I'm using espresso
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2',
and uiAutomatorViewer to double check the R.id's~ and class names.
I've encountered a problem while trying to edit the text in the element with no R.id but with class name android.widget.EditText:
android.support.test.espresso.NoMatchingViewException: No views in hierarchy found matching: (with id: com.mydosesmart:id/til_name and an instance of android.widget.FrameLayout and an instance of android.widget.EditText)
Problem is that the element with class name android.widget.EditText has no R.id.. This class name is not unique for this view, this element with class name android.widget.EditText has a parent element with a unique R.id..
On the login view in the app, two elements have the class name android.widget.EditText therefore I can't call this element just by class name. I want to call it like that:
within the element with R.id.til_name find the element with the class name android.widget.EditText. Below is the code I'm using now and it fails.
ViewInteraction textInputEditText2 = onView(
allOf(withId(R.id.til_name), instanceOf(Class.forName("android.widget.FrameLayout")), instanceOf(Class.forName("android.widget.EditText"))));
textInputEditText2.perform(replaceText("testespresso"), closeSoftKeyboard());
That fails too:
ViewInteraction textInputEditText2 = onView(
allOf(withId(R.id.til_name), instanceOf(Class.forName("android.widget.EditText"))));
textInputEditText2.perform(replaceText("testespresso"), closeSoftKeyboard());
As plenty of elements in the app I'm testing have no designated R.id I would like to find an easy way to call them for testing purposes.
After trying dozens of different matchers in all possible combinations I've found the answer to my question. So far it seems to be universal:
onView(allOf(withClassName(containsString(EditText.class.getSimpleName())), isDescendantOfA(withId(R.id.til_name))))
.perform(replaceText("testespresso "), closeSoftKeyboard());
Using isDescendantOfA we don't need to worry if the element we are looking for has a parent/grandparent with R.id, it just needs to be lower in a hierarchy.
I think you should try this (to find your EditText with no ID but with parent with unique known id) :
allOf(withParent(withId(R.id...)), withClassName(containsString(EditText.class.getName())))
Update based on new info: to match with indirect parent (R.id.... is where to place indirect parent's id) :
allOf(isDescendantOfA(withId(R.id...)), withClassName(containsString(EditText.class.getName())))
I'm testing a hybrid app, where each view has a web view.
In one of these web views I have a list of elements with the same attribute. They have the same xpath locator that is something like:
//h4[contains(#data-role, 'product-name')]
I want to create a list of these elements and iterate through them, count them, get their attributes.
In the documentation, I found two similar methods:
findElement(locator, value)
and
findMultipleElements(locator, value)
Though it's totally unclear to me how to use it. I tried to find examples on it but with no success.
Could someone help me with this?
Here is the solution that I have found.
#kaqqao is right that findMultipleItems call returns Atom<List<ElementReference>> that is not usable with onWebView() because there you have only withElement() that accepts either Atom<ElementReference> or just ElementReference
What you can do though is perform your action that find multiple items and just get results from your Atom. This is how it works internally if you check the source of doEval method inside Web.java for espresso.
val elements = with(AtomAction(findMultipleElements(
Locator.XPATH,
"YOUR_COMPLEX_XPATH"
), null, null)) {
onView(ViewMatchers.isAssignableFrom(WebView::class.java)).perform(this)
this.get()
}
This code will give you List<ElementMatcher>.
Then just run it as
elements.forEach {
onWebView().forceJavascriptEnabled().withElement(it).perform(webClick())
}
Can you try something like that? Since what you should care about is really the ElementReference and you can iterate the lsit returned from findMultipleElements with simple for/foreach statement:
yourList = findMultipleElements(locator, value);
yourList.size(); //this will get you the count of found elements with that locator
for(Atom<ElementReference> item : yourList ){
item.getAttribute...
//and whatever you want
}
I want to check displaying of Save €XX in the list. Save €XX is a TextView that can be VISIBLE or INVISIBLE. I use JUnit 4 and Espresso 2.2.1.
I tried to check it like this:
onView(withText(startsWith("Save"))).check(matches(isDisplayed()));
but always get an error:
android.support.test.espresso.AmbiguousViewMatcherException: 'with text: a string starting with "Save"' matches multiple views in the hierarchy.
Is there a way to if the TextView exists in the ListView with Espresso?
UPDATE
I also tried to use onData:
onData(hasToString(startsWith("Save")))
.inAdapterView(withId(R.id.suggestion_list_view)).atPosition(0)
.check(matches(isDisplayed()));
but it seems that onData works with data layer but not the view layer. Therefore, I receive the error:
java.lang.RuntimeException: No data found matching: with toString() a string starting with "Save" contained values: <[Data: ...]>
After several tries, I found the way.
In this case, we should use a combined approach and work with both data and view layers. We access the ListView by ID and choose the first item. Then check it for the 'Save' text.
onData(anything())
.inAdapterView(withId(R.id.list_view))
.atPosition(0)
.onChildView(withId(R.id.suggestion_saving))
.check(matches(withText(startsWith("Save"))));
Works like a charm. Enjoy!
I am trying to test my app which uses ViewPager. Each page contains fragments but these fragments are not always visible. I want to check visibility of a fragment in the currently visible page.
onView(withId(R.id.container_weather))
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)));
But the problem is that espresso looks are all the pages not just the current page and I get the following error:
android.support.test.espresso.AmbiguousViewMatcherException: 'with id: eu.airpatrol.android:id/container_weather' matches multiple views in the hierarchy...
I had the same problem, however using the condition isCompletelyDisplayed() solved this problem as it only takes into account the on-screen views.
So, something like this should work:
onView(allOf(withId(R.id.container_weather), isCompletelyDisplayed()))
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)));
Note: isDisplayed() works too in some cases but it also takes views off-screen into account and won't work if the ViewPager has any other page pr fragment loaded with the same view id.
Your tests are failing because of multiple elements with the same id. You can combine conditions using allOf(...). Then use isDisplayed() to check that matched view is visible on the screen. Below example can work:
onView(allOf(
withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE),
withId(R.id.container_weather)))
.check(matches(isDisplayed()));
Ran into this exact same problem. I was fortunate because the view hierarchies in my ViewPager can be easily identified by their siblings, so I was able to solve this using the hasSibling matcher, like so:
onView(
allOf(
hasSibling(withId(R.id.some_sibling)),
withId(R.id.field_to_test)
)
).perform(replaceText("123"));
Not a perfect solution as it can be slightly brittle, but in my case I think it was an acceptable compromise.
I had similar problem, where I was reusing the button layout and it was giving me a matches multiple views in the hierarchy exception.
So the easy work around I did was to create 2 different screens and have 2 different methods with different text.
Withdraw Screen:
public WithdrawScreen clickWithdraw() {
onView(allOf(withId(R.id.save_button), withText("Withdraw")))
.perform(click());
return this;
}
Deposit Screen:
public DepositScreen clickDeposit() {
onView(allOf(withId(R.id.save_button), withText("Deposit")))
.perform(click());
return this;
}
and in my tests, I create a new instance of both screens and call the above methods based on screen reference which is a bit easy to test for.
WithdrawScreen withdrawInstance = new WithdrawScreen();
withdrawInstance.clickWithdraw();
DepositScreen depositInstance = new DepositScreen();
depositInstance.clickDeposit();
The point was they were using same id - R.id.save_button for button and I was replacing text of button based on visibility of the fragment we are on.
Hope it helps.
I'm trying to implement an UIAutomator testcase with a general method to perform a click on a ListView item (regardless of the type of viewgroup holding the listitem).
Currently I have following code, but it keeps on clicking the first item.
public void clickListViewItem(int index) throws UiObjectNotFoundException {
UiObject listview = new UiObject(new UiSelector().className("android.widget.ListView"));
if(index <= listview.getChildCount()){
listview.getChild(new UiSelector().index(index)).click();
}else{
throw new UIObjectNotFoundException("Index is greater than listSize");
}
}
I got it to work with following code, it is based on the clickable attribute of an UISelector:
listview.getChild(new UiSelector().clickable(true).index(index)).click();
The developer page implements a similar scenario, found here - although this assumes there is some identifying feature that exists in the child by which to select (like in example below, a string "Apps"):
If more than one matching element is found, the first matching element in the layout hierarchy is returned as the target UiObject. When constructing a UiSelector, you can chain together multiple properties to refine your search. If no matching UI element is found, a UiAutomatorObjectNotFoundException is thrown.
You can use the childSelector() method to nest multiple UiSelector instances. For example, the following code example shows how your test might specify a search to find the first ListView in the currently displayed UI, then search within that ListView to find a UI element with the text property Apps.
val appItem: UiObject = device.findObject(
UiSelector().className("android.widget.ListView")
.instance(0)
.childSelector(
UiSelector().text("Apps")
)
)