UI Tests are asynchronous and dynamic. Issues and tips.

TL;DR Use snapshot() method of XCUIElement in order to get consistent element information.

Asynchronous nature of UI Tests

Tests happen in a separate process, Test Runner, that doesn’t have access to actual views of our App. Runner talks to App using some special communication channel, probably inter-process mechanism. Our code dispatches requests to this mechanism, and then the mechanism talks to App on behalf of our code. This makes our tests asynchronous in 2 ways. First, the actual execution of a request will happen some time after our code is executed, and we don’t know when. Second, App’s state and memory changes are not blocked by our test’s code. When we do anything related to accessibility elements, e.g. print debug description or tap on element, we don’t know when it will actually happen and at what state elements will be accessed.

Implicit dynamic nature of XCUIElement

Consider this piece of code:

let cell = app.cells.matching(identifier: "mainScreenSimilarCell").element
print(cell.frame)
print(cell.identifier)

It stores an element into variable cell, and then prints 2 fields of it. What I’ve had expected, is that frame and identifier would be read from a single representation of an element. But let’s take a look at test output

Notice “Find the Cell” output twice. Even though we already store element in our variable, it would still re-find element each time we access a property of it. Because of that, what we see about properties may and will be out of sync. It may even find a different element!

Of course, there are some fields of XCUIElement that read directly from Runner’s memory, without dispatching inter-process requests. But as far as I tested, all of test related properties and fields do the cycle, see “Find” and “Checkin” outputs below:

Example issue: difficult debugging

For different use cases like debugging some strange “cell accessibility identifier reuse” problem, we may want to print multiple different fields of XCUIElement at once, just to see what happens. For that we may use debugDescription, but the problem with it is that it’s just a huge string and extracting something specific from it would be a pain. And also, it’s event more asynchronous! I mean it’s slow, because it needs to snapshot whole app hierarchy, event if you just care about single small element. If your problem is hard to catch, debugDescription will probably be too slow and will miss it.

Solution

Use XCUIElements snapshot().

let snapshot = try cell.snapshot()
print(snapshot.frame)
print(snapshot.identifier)

Single find, 2 print outputs, just what we want. Snapshot captures all the XCUIElementAttributes fields and gives us representation that doesn’t change when we access it multiple times. It’s a quick and as easy to use as XCUIElement.

What led me to this investigation

Sometimes, when interacting with element (cell) queried by concrete identifier, the UI Test or accessibility system would find different, wrong element, with different identifier.

>     t =     9.13s Find the "mainScreenSimilarCell" Cell
> Cell, {{211.0, 615.0}, {186.0, 189.0}}, identifier: 'mainScreenScreenshotsCell'

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: