KakaoCup / Kakao

Nice and simple DSL for Espresso in Kotlin

Home Page:https://kakaocup.github.io/Kakao/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Potential performance optimisation around hierarchy-traversing matchers

lwasyl opened this issue · comments

Tl;dr: It's possible that Kakao could be more performant by reordering matchers to apply faster matchers first.


Since Kakao wraps Espresso, Espresso's view finding mechanism is used for actions and assertions. This mechanism will always iterate through entire view hierarchy looking for all views matching the requested predicate (source).

Most matchers should be pretty fast, however some of them will also iterate some part of view hierarchy as part of their check. For example HasDescendantMatcher or IsDescendantOfAMatcher will both go up or down the view hierarchy looking for views that match the requested descendant/ancestor. This means complexity of those matchers is not constant, contrary to simple matchers like WithIdMatcher. Moreover, when these matchers are nested, the overall performance decreases dramatically — for nested isDescendantOfA matchers, Espresso will go through every view in hierarchy, first matcher will go up in hierarchy and execute the nested matcher, which will also go up the hierarchy — I believe the complexity is non-linear in that case.

This is all unavoidable to an extent, but if those slow matchers are used within composite matchers like allOf() or anyOf(), the developer can easily improve Espresso performance by simply putting the expensive, view-traversing matchers later on the list. That way Espresso will short-circuit as soon as it can, potentially not invoking the expensive matcher at all.

If I read the code correctly, Kakao will sometimes put the slow matcher first on the list that is effectively transformed into an allOf() with same ordering, for example

val vb = ViewBuilder().apply {
isDescendantOfA { withMatcher(parentMatcher as Matcher<View>) }
builder(this)
}
or
edit = KEditText {
isDescendantOfA(function)
isAssignableFrom(EditText::class.java)
}
. Simply switching those lines would improve the performance essentially for free, with no obvious downsides. The faster matchers will run first and short-circuit the check if they fail, and the slower matcher will run a couple of times instead of for every single view.

This might look like a small change, but especially with nested matchers the performance impact can be significant —  in one project with ~270 UI tests, simply putting isDescendantOfA and hasDescendant matchers last in allOf calls cut total test run time in half (the project doesn't use Kakao).

Will be available in next release. Thanks.

Thanks! If you have any measurements/numbers before and after the changes, I'd be curious to hear if you saw any improvements 🙂

@lwasyl we have 1k+ UI tests in prod. Ill tell ya about performance boost changes.