2

I have a UITableview with custom view that appears in the viewForHeaderInSection delegate method. It appears and functions fine when running the app.

While doing UITests, I've noticed that this custom view identifier doesn't appear in the view hierarchy (unless they're on screen OR nearly on screen). As a result, I cannot UITest headers that are further down in the tableview.

I've found this article from a few years ago https://tiyachows.medium.com/xcuitest-and-its-nuances-3db6fac6f5dc and it says

If the header element is offscreen and then later scrolled into view. The app hierarchy is not refreshed with the id of that header and the UI tests will not be able to access that element.

which doesn't give me confidence that I'm able to UITest my headers.

Is this a known issue? Are there any known work arounds besides manually scrolling to a header?

p0ny
  • 317
  • 2
  • 10

3 Answers3

0

This is not an issue per se to have to work around it. Afaik this is a good practice when developing mobile applications not to load all the elements that can be presented on an app page to save memory. Since you are doing UI tests you should try to simulate what a real user would do with your interactions - in this case if the element is offscreen it is recommended that you scroll to it. Imagine that the page cannot be scrolled for some reason - a test will never catch that if you are able to interact with and validate elements offscreen directly.

drunkencheetah
  • 354
  • 1
  • 8
0

You’ll need to manually scroll to them. When using a standard table, XCUITest is smart enough to scroll to items not yet visible, but once you get custom you’ll need to get custom with your actions.

I’d suggest a scrollTo(label: String, maxScrolls: Int) function. If you see the thing, you’re done! If you don’t, scroll again unless you’ve hit your max threshold (you don’t want to scroll forever if something doesn’t exist in the table at all).

If you’re tapping once you reach your item you should know this can be flaky; depending on scroll speed and screen size, you may run into a case where something is barely on screen, resulting in it being visible to XCUITest, but not able to be tapped on yet (barely appearing on screen). To account for this case I check if something is tappable before attempting . If it isn’t (and has already passed the visible check) I’ll perform a tiny, tiny, tiny scroll. If you’re not tapping, ignore this paragraph.

Mike Collins
  • 4,108
  • 1
  • 21
  • 28
  • I guess I'm a little hesitant to 100% believe this as I have a custom cell that appears fine in the down in the hierarchy when off screen. It just seems to be headers that are the issue. There's also a discussion https://developer.apple.com/forums/thread/16810 where an Apple framework engineer says "You are not supposed to have to scroll manually. Interacting with an element that is not currently visible in the scroll view is expected to implicitly first scroll the element to be visible, without requiring effort on your part" – p0ny Jul 21 '22 at 22:11
  • 1
    I agree with Mike on this. Your point is also valid but "seeing" a custom cell that is offscreen does not mean you should automatically see all offscreen elements in the hierarchy or that it should be "seen" at all. The discussion you mentioned is right - partially. First of all to be able to "automagically" swipe to the element it needs to already `exist` in the hierarchy. Second - based on my experience with it, it doesn't always work properly even if the element exists. – drunkencheetah Jul 22 '22 at 09:36
  • 1
    @drunkencheetah 100%. One of the most important lessons of XCUITest is to not expect things to work as you’d expect them. – Mike Collins Jul 22 '22 at 12:50
0

Yes. I encountered this too when I started working on XCTest.

The solution is to do the following:

  1. Make a custom swipe function (extension) -> this is needed because Apple often changes the size of their swipe function and it would mess up the custom scroll function otherwise. It could have an option to scroll in any of the following directions: .up, .down, .right, .left -> though .down should be default.
  2. Make a scrollsIntoView function (extension). for _ in 0...maxSwipes and then check if item exists && isHittable. If not keep scrolling and swipeCount += 1. If so, return true. If we don't find the element in the maxSwipes, then return false. Returning a bool allows you to check if you see something in only a few swipes and pivot to a different action if it's not there without failing the test. But you could also assert against the bool value directly in the test case.
  3. If you want to get more advanced, you can account for the scrollDomain, which is basically the area of the app that we're scrolling on. This is helpful for horizontal scrolling in a carousel or something.

Also worth pointing out that the swipe direction is the opposite of the scroll direction.

Good luck!