8

I have 3 screens, lets say,

  1. Login
  2. Forgot Password
  3. Help screen

By default the Login screen open when the app starts. The Forgot Password screen is shown when you click on the Forgot Password button, and Help Screen opens when the Help link is clicked.

Can I somehow open the the Forgot Password Screen directly without going through the procedure of clicking the button using XCUITest?

I am suggesting something in the same lines as passing an adb intent to open a View directly.

anotherCoder
  • 712
  • 3
  • 11
  • 25

1 Answers1

12

As far as I know, you can't go directly to the second screen using XCUITest Framework. Anyway, documentation states:

UI testing exercises your app's UI in the same way that users do without access to your app's internal methods, functions, and variables. This enables your tests to see the app the same way a user does, exposing UI problems that users encounter.

Which means that if user of your app can't reach the second screen directly, why could your UI tests.

I know it's time consuming to wait to go to the second screen when you run your tests, but you can bypass writing it for every test. To write it only once, in your XCTestCase class write a function where you implement calling a second screen and call that function in setUp() method. Then, the process of skipping the first screen will be called every time you run a test because setUp() method is called before every test run.

EDIT

After reading your comment, I could think of one hacky solution. You can communicate with your app from your tests using Launch Environment and/or Launch Arguments. So, in your XCTestCase class, set up argument and environment:

class ForgotPasswordUITest: XCTestCase {
    let app = XCUIApplication()

    override func setUp() {
        app.launchArguments += ["UI-TESTING"]
        app.launchEnvironment["pageToGo"] = "forgotPassword"
        app.launch()
    }
}

Then, in your ViewController, write these computed properties:

var isUiTestingEnabled: Bool {
    get {
        return ProcessInfo.processInfo.arguments.contains("UI-TESTING")
    }
}

var shouldShowForgotPassword: Bool {
    get {
        return ProcessInfo.processInfo.environment["pageToGo"] == "forgotPassword"
    }
}

var shouldShowHelpScreen: Bool {
    get {
        return ProcessInfo.processInfo.environment["pageToGo"] == "helpScreen"
    }
}

And in viewDidLoad() method, you can have something like this:

    if isUiTestingEnabled {
        if shouldShowForgotPassword {
            let storyboard = UIStoryboard(name: "Main", bundle: nil)
            let secondViewController = storyboard.instantiateViewController(withIdentifier: "ForgotPasswordViewController")
            self.present(secondViewController, animated: true, completion: nil)
        } else if shouldShowHelpScreen {
            let storyboard = UIStoryboard(name: "Main", bundle: nil)
            let secondViewController = storyboard.instantiateViewController(withIdentifier: "HelpScreenViewController")
            self.present(secondViewController, animated: true, completion: nil)
        }
    }

Note: this is a really dirty hack and it is not recommended way of writing UI tests.

Mladen
  • 2,070
  • 1
  • 21
  • 37
  • 1
    @logoman Thats a good answer. and I agree with you completely. But there are certain times when you have tested a flow already and don't really need to do it over and over again. Thereby speeding up the tests. I am upvoting your answer, but I will wait for more answers. Even if there is a hacky way to do it. – anotherCoder Mar 01 '18 at 17:59
  • @Vinayaka, I edited my answer adding one hacky solution. – Mladen Mar 01 '18 at 22:22
  • @lagoman using the launch arguments and/or launch environment to communicate from the UI tests to the app is an ingenious solution, nice. I would be skeptical to adopt it though, as you say it's a bit of a hack. What would worry me is the extra code we have to write to establish this relationship. I don't like "polluting" production code with code only meant for the tests. As always in software development it's a matter of tradeoffs. Is the UI tests suite faster run time worth the extra code to maintain? It depends on the project :) – mokagio Feb 18 '19 at 05:47
  • 1
    @mokagio, agreed :) – Mladen Feb 18 '19 at 13:14
  • @lagoman i think your solution through which we can directly go to the screen was perfect as i don't see any otherway than using environment/ launch argument. – jogshardik Mar 19 '19 at 11:32
  • @lagoman Is it possible to change the `pageToGo` variable during the text. For example, I'd like to test the navigation within a certain view. How might I properly navigate and test it occured? – Bonteq Jun 24 '20 at 19:26
  • @Bonteq Not sure that it is possible. Let me try to understand what you need. If you have something like this: `Page1` --> `Page2` --> `Page3` --> `Page4` and you'd like to go to `Page2` directly, after which you'd like to go to `Page4` directly. Instead of setting `app.launchEnvironment["pageToGo"]` two times (which I don't think is possible, but please read the docs), I would set something like `app.launchEnvironment["secondPageToGo"] = "Page4"` where I'd read `"secondPageToGo"` from the `environment` and then I'd decide where to go. Would this be helpful? – Mladen Jun 24 '20 at 20:38
  • @lagoman I'd like to go to `Page2` directly, and then test the navigation that is possible within that particular view, which, in my case, is a back button to `Page1` and the next button to `Page3` – Bonteq Jun 25 '20 at 03:46
  • If I were to initialize the `setUp()` within my test with an additional environment variable like `app.launchEnvironment["secondPageToGo"] = "Page2"`, how would I differentiate how to navigate to this view at an appropriate time if this state is set during `setUp`? – Bonteq Jun 25 '20 at 04:21
  • It doesn't matter where this state is set (either in `setUp` or the test itself), it matters only if you set it _before_ calling `app.launch()`. Calling `launch` will basically run your app (in another process) which can be able to read launch environments and arguments (see the answer how). From the app itself, you should be able to decide what to do. If this still doesn't help you solve your problems, please ask a question referring to this answer and explaining what you have tried so far. – Mladen Jun 25 '20 at 06:35