<repo_name>intere/UITestKit<file_sep>/UITestKit.podspec
#
# Lint this before submitting:
# `pod lib lint --allow-warnings UITestKit.podspec`
#

Pod::Spec.new do |s|
  s.name             = 'UITestKit'
  s.version          = '0.1.2'
  s.summary          = 'Programmatic UI Tests (not using the Apple UI Test Framework).  XCTest-based tests.'

# This description is used to generate tags and improve search results.
#   * Think: What does it do? Why did you write it? What is the focus?
#   * Try to keep it short, snappy and to the point.
#   * Write the description between the DESC delimiters below.
#   * Finally, don't worry about the indent, CocoaPods strips it!

  s.description      = <<-DESC
This library solves the problem of having to maintain Apple's UI Tests, but lets you automate UI Tests for
integration testing.  By using this library, you are no longer subject to the limitations of the UI Tests
that Apple provides a framework for.  You can run UI Tests as part of a normal Unit Test target.  The strategy
is similar to many other language UI Testing frameworks, timers and conditional checks.  See the example project
to see how it starts you off.

I wrote this library, because it's the basic building blocks that I've used in so many iOS projects now.
I've had issues with the Apple UI Test framework and it's time to apply the DRY (Don't Repeat Yourself)
principle.
                       DESC

  s.homepage         = 'https://github.com/intere/UITestKit'
  s.license          = { :type => 'MIT', :file => 'LICENSE' }
  s.author           = { '<NAME>' => '<EMAIL>' }
  s.source           = { :git => 'https://github.com/intere/UITestKit.git', :tag => s.version.to_s }
  s.social_media_url = 'https://twitter.com/intere'

  s.ios.deployment_target = '9.0'
  s.swift_version = '4.2'

  s.source_files = 'UITestKit/**/*'
  s.frameworks = 'XCTest'

end
<file_sep>/Example/UITestKit_ExampleTests/ExampleBaseUITest.swift
//
//  ExampleBaseUITest.swift
//  UITestKit_ExampleTests
//
//  Created by <NAME> on 11/5/17.
//  Copyright © 2017 CocoaPods. All rights reserved.
//

@testable import UITestKit_Example
import UITestKit
import XCTest

class ExampleBaseUITest: UITestKitBase {

    /// Gets you the `MyTabBarVC` if it's visible
    var myTabBarVC: MyTabBarVC? {
        return tabBarVC as? MyTabBarVC
    }

    /// Gets you the `CircleTabVC` if it's the topVC
    var circleTabVC: CircleTabVC? {
        return topVC as? CircleTabVC
    }

    /// Gets you the `SquareTabVC` if it's the topVC
    var squareTabVC: SquareTabVC? {
        return topVC as? SquareTabVC
    }

    /// Gets the `ShapesTableViewController`
    var shapeTableVC: ShapesTableViewController? {
        return topVC as? ShapesTableViewController
    }

}

// MARK: - API

extension ExampleBaseUITest {

    @discardableResult
    /// Opens the Circle Tab.
    ///
    /// - Returns: The CircleTab if we were able to open it.
    func openCircleTab() -> CircleTabVC? {
        guard let myTabBarVC = myTabBarVC else {
            XCTFail(topVCScreenshot)
            return nil
        }

        myTabBarVC.selectedIndex = 0
        XCTAssertTrue(waitForCondition({ self.circleTabVC != nil }, timeout: 10), topVCScreenshot)

        return circleTabVC
    }

    @discardableResult
    /// Opens the Square Tab.
    ///
    /// - Returns: The SquareTab if we were able to open it.
    func openSquareTab() -> SquareTabVC? {
        guard let myTabBarVC = myTabBarVC else {
            XCTFail(topVCScreenshot)
            return nil
        }

        myTabBarVC.selectedIndex = 1
        XCTAssertTrue(waitForCondition({ self.squareTabVC != nil }, timeout: 10), topVCScreenshot)

        return squareTabVC
    }

    @discardableResult
    /// Opens the shape tab and hands you back the VC.
    ///
    /// - Returns: The `ShapesTableViewController`
    func openShapeTab() -> ShapesTableViewController? {
        guard let myTabBarVC = myTabBarVC else {
            XCTFail(topVCScreenshot)
            return nil
        }

        myTabBarVC.selectedIndex = 2
        XCTAssertTrue(waitForCondition({ self.shapeTableVC != nil}, timeout: 10), topVCScreenshot)

        return shapeTableVC
    }

}
<file_sep>/UITestKit/Classes/UITestKitBase.swift
//
//  UITestKitBase.swift
//  UITestKit
//
//  Created by <NAME> on 11/5/17.
//

import Foundation
import UIKit
import XCTest

open class UITestKitBase: TestKitBase {

    /// Should the `pauseForUIDebug` function actually pause?
    /// This variable is useful for when you want to watch a UI Run
    open var shouldPauseUI = false

    /// If `shouldPauseUI` is enabled, this is how long a `pauseForUIDebug()` call lasts (in seconds).
    open var pauseTimer: TimeInterval = 0.5

    /// if `pauseForUIDebug` is enabled, then this will pause for `pauseTimer` seconds
    /// if not, then it is a no-op.
    open func pauseForUIDebug() {
        guard shouldPauseUI else {
            return
        }
        waitForDuration(pauseTimer)
    }

    /// Tells you the type of the top ViewController
    open var topVCType: String {
        guard let topVC = topVC else {
            return "No Top VC"
        }
        return String(describing: topVC.classForCoder)
    }

    /// Gets you the VC that is the currently visible VC (top)
    open var topVC: UIViewController? {
        return type(of: self).topVC
    }

    /// Takes a screenshot and returns the filename that it wrote to (which contains the topVCType)
    open var topVCScreenshot: String {
        guard let screenshot = UIApplication.shared.screenShot else {
            return topVCType
        }

        let time = Int(Date().timeIntervalSince1970 * 1000)
        let filename = "/tmp/\(topVCType)_\(time).png"

        do {
            guard let pngData = screenshot.pngData() else {
                return topVCType
            }
            try pngData.write(to: URL(fileURLWithPath: filename))
            return filename
        } catch {
            print("ERROR writing file \(filename): \(error.localizedDescription)")
        }

        return topVCType
    }

    /// Gets you a reference to the TabBarVC for the app (so you can set
    /// what tab is visible, get the currently presented VC, etc)
    open var tabBarVC: UITabBarController? {
        guard let rootVC = UIApplication.shared.keyWindow?.rootViewController else {
            return nil
        }

        if let tabVC = rootVC as? UITabBarController {
            return tabVC
        }

        return nil
    }

    /// Gets you the VC that is the currently visible VC (top)
    open class var topVC:  UIViewController? {
        let visibleVC = getVisibleViewController()

        if let navVC = visibleVC as? UINavigationController {
            return navVC.topViewController
        }

        if let tabVC = visibleVC as? UITabBarController {
            if let navVC = tabVC.selectedViewController as? UINavigationController {
                return navVC.topViewController
            }
            return tabVC.selectedViewController
        }

        return visibleVC
    }

    /// Traverses the provided `rootViewController` to find the currently visible view controller.
    ///
    /// - Parameter rootViewController: The root Viewcontroller to search.
    /// - Returns: The currently visible ViewController if it could be determined.
    open class func getVisibleViewController(_ rootViewController: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {

        var rootVC = rootViewController
        if rootVC == nil {
            rootVC = UIApplication.shared.keyWindow?.rootViewController
        }

        if rootVC?.presentedViewController == nil {
            return rootVC
        }

        if let presented = rootVC?.presentedViewController {
            if presented.isKind(of: UINavigationController.self) {
                let navigationController = presented as! UINavigationController
                return getVisibleViewController(navigationController.viewControllers.last!)
            }

            if presented.isKind(of: UITabBarController.self) {
                let tabBarController = presented as! UITabBarController
                return tabBarController.selectedViewController!
            }

            return getVisibleViewController(presented)
        }
        return nil
    }

    /// Disables UI Animations
    open func disableAnimations() {
        UIView.setAnimationsEnabled(false)
    }

    /// Enables UI Animations
    open func enableAnimations() {
        UIView.setAnimationsEnabled(true)
    }

    /// Modally presents the provided view controller (without animations).
    ///
    /// - Parameter viewController: The view controller you want to present.
    open func presentModally(viewController: UIViewController) {
        topVC?.present(viewController, animated: false)
    }

}

extension UIApplication {

    /// Takes a screenshot of the application.
    var screenShot: UIImage?  {
        return keyWindow?.layer.screenShot
    }
}

extension CALayer {

    /// Takes a screenshot of the layer.
    var screenShot: UIImage?  {
        let scale = UIScreen.main.scale
        UIGraphicsBeginImageContextWithOptions(frame.size, false, scale)

        guard let context = UIGraphicsGetCurrentContext() else {
            return nil
        }

        render(in: context)
        let screenshot = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return screenshot
    }
}
<file_sep>/Example/UITestKit_ExampleTests/ShapeTests.swift
//
//  ShapeTests.swift
//  UITestKit_ExampleTests
//
//  Created by <NAME> on 12/29/18.
//  Copyright © 2018 CocoaPods. All rights reserved.
//

@testable import UITestKit_Example
import XCTest

class ShapeTests: XCTestCase {

    func testShapeImages() {
        for shape in Shape.allCases {
            XCTAssertNotNil(shape.image, "\(shape) does not have an image")
        }
    }

}
<file_sep>/UITestKit/Classes/TestKitBase.swift
//
//  TestKitBase.swift
//  UITestKit
//
//  Created by <NAME> on 11/5/17.
//

import XCTest

/// A Base Class that provides some very basic functions for sleeping arbitrarily and waiting for a specific condition to occur (or timing out if that doesn't happen)
open class TestKitBase: XCTestCase {

    /// Waits for the desired amount of time (and frees up the UI thread while it waits)
    ///
    /// - Parameter timeout: The length of time to wait for
    public func waitForDuration(_ timeout: TimeInterval) {
        waitForCondition({ false }, timeout: timeout)
    }

    /// Waits for the provided condition block to evaluate to true, or the timeout amount of time to elapse.
    ///
    /// - Parameters:
    ///   - condition: The block to evaluate for truthy/falsy.
    ///   - timeout: The length of time to stop querying the block and just return false.
    /// - Returns: True if the block evaluated truthy in the allotted time, false otherwise.
    @discardableResult
    public func waitForCondition(_ condition: () -> Bool, timeout: TimeInterval) -> Bool {
        var done = false
        let startTime = NSDate()
        while !done {
            RunLoop.current.run(mode: .default, before: Date(timeIntervalSinceNow: 0.1))

            done = condition()
            if done {
                break
            }

            if -startTime.timeIntervalSinceNow > timeout {
                return false
            }
        }
        return true
    }

}
<file_sep>/Example/UITestKit/ViewControllers/Views/ShapeTableViewCell.swift
//
//  ShapeTableViewCell.swift
//  UITestKit_Example
//
//  Created by <NAME> on 12/29/18.
//  Copyright © 2018 CocoaPods. All rights reserved.
//

import Cartography
import UIKit

class ShapeTableViewCell: UITableViewCell {

    var shape: Shape? {
        didSet {
            contentChanged()
        }
    }

    let titleLabel = UILabel()
    let shapeImageView = UIImageView()

    override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        buildCell()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        buildCell()
    }

    override func prepareForReuse() {
        super.prepareForReuse()
        titleLabel.text = nil
        shapeImageView.image = nil
    }

}

// MARK: - Implementation

private extension ShapeTableViewCell {

    func buildCell() {
        [titleLabel, shapeImageView].forEach { contentView.addSubview($0) }

        constrain(contentView, titleLabel, shapeImageView) { view, titleLabel, shapeImageView in
            shapeImageView.top == view.top + 4
            shapeImageView.left == view.left + 16
            shapeImageView.bottom == view.bottom - 4
            shapeImageView.width == 80
            shapeImageView.height == 80

            titleLabel.left == shapeImageView.right + 16
            titleLabel.right == view.right - 16
            titleLabel.centerY == shapeImageView.centerY
        }
    }

    func contentChanged() {
        guard let shape = shape else {
            return
        }
        titleLabel.text = shape.rawValue
        shapeImageView.image = shape.image
    }
}
<file_sep>/Example/Podfile
use_frameworks!
platform :ios, '9.3'


target 'UITestKit_Example' do

  pod 'Cartography'

  target 'UITestKit_ExampleTests' do
    inherit! :search_paths
    pod 'UITestKit', :path => '../'

  end
end
<file_sep>/README.md

<img src="https://github.com/intere/UITestKit/blob/master/Example/UITestKit/Media.xcassets/AppLogo.imageset/AppLogo.png?raw=true" width="150">

# UITestKit

### An iOS Library for UI Testing

## Features
- UI Testing from a vanilla `XCTest` target
    - Enable / Disable UI Animations
    - Take screenshots on test assertion failures
    - `waitForCondition` function to free up the UI thread and wait for your block condition to become true (or timeout and move on)
    - `pauseForUIDebug()` function to allow you to pause a (configurable) amount of time between steps in your tests
        - function becomes a no-op when `shouldPauseUI` is false (the default)
    - variables to get you the top view controller
- Example App to demonstrate capabilities

[![Build Status](https://travis-ci.org/intere/UITestKit.svg?branch=develop)](https://travis-ci.org/intere/UITestKit)
[![Documentation](https://intere.github.io/UITestKit/badge.svg)](https://intere.github.io/UITestKit/index.html)
[![Platform](https://img.shields.io/badge/Platforms-iOS-lightgray.svg?style=flat)](http://cocoadocs.org/docsets/UITestKit)
[![CocoaPods](https://img.shields.io/cocoapods/v/UITestKit.svg)](https://cocoapods.org/pods/UITestKit)  
 [![CocoaPods](https://img.shields.io/cocoapods/dt/UITestKit.svg)](https://cocoapods.org/pods/UITestKit) [![CocoaPods](https://img.shields.io/cocoapods/dm/UITestKit.svg)](https://cocoapods.org/pods/UITestKit)

## Why not use XCUITests?
- UITestKit lets you interact with the application code directly from test code
    - Easier mocking of model objects
    - Easier invocation of ViewControllers
    - Lower level access to your application
- UITestKit provides convenience variables for taking screenshots from failures
- UITestKit can be far less rigid than XCUITests

## Example
To run the example project, clone the repo, and run `pod install` from the Example directory first.
![Example Tests](https://user-images.githubusercontent.com/2284832/50541549-86a3e080-0b65-11e9-95d2-176b9ce3164b.gif)

## Requirements
- iOS 9.0 or higher

## Installation

UITestKit is available through [CocoaPods](http://cocoapods.org). To install
it, simply add the following line to your Podfile:

```ruby
pod 'UITestKit'
```

### Code Examples

```swift
/// Pre-test initialization
override func setUp() {
    super.setUp()
    // Don't show view transitions - this will help prevent timing related failures
    disableAnimations()
    // pause when pauseForUIDebug() is called
    shouldPauseUI = true
    // how long to pause for when pauseForUIDebug() is called
    pauseTimer = 0.3

    // Now do other setup tasks
}
```

```swift
/// Tests logging in to the application
func testLoginSuccess() {

    // Verify that we're at the Main VC or fail and take a screenshot
    XCTAssertTrue(waitForCondition({ self.mainVC != nil }, timeout: 3), topVCScreenshot)
    pauseForUIDebug()
    mainVC?.loadEmailLoginScreen()

    // Verify that we're at the Sign in VC or fail and take a screenshot
    XCTAssertTrue(waitForCondition({ self.signInVC != nil}, timeout: 1), topVCScreenshot)
    pauseForUIDebug()

    // Simulate typing email
    if let emailText = signInVC?.emailText {
        emailText.text = "<EMAIL>"
        pauseForUIDebug()
    }

    // simulate typing password
    if let passwordText = signInVC?.passwordText {
        passwordText.text = "<PASSWORD>"
        pauseForUIDebug()
    }

    // log in
    signInVC?.login()

    // Verify that we've signed in successfully and are at the Welcome VC or fail and take a screenshot
    XCTAssertTrue(waitForCondition({ self.welcomeVC != nil}, timeout: 5), topVCScreenshot)
}
```

### Best Practices
- It is recommended to create a `BaseUITest` class that specializes `UITestKitBase` and provides convenience variables for your application's view controllers:

```swift
/// Gets you the `SquareTabVC` if it's the topVC
var squareTabVC: SquareTabVC? {
    return topVC as? SquareTabVC
}

/// Gets the `ShapesTableViewController`
var shapeTableVC: ShapesTableViewController? {
    return topVC as? ShapesTableViewController
}
```

- It is recommend that you use the screenshot capability when checking for specific UI's to be visible or in a specific UI state:

```swift
XCTAssertTrue(waitForCondition({ self.shapeTableVC != nil }, timeout: 1), topVCScreenshot)
```

`topVCScreenshot` will produce failures like this:

![Sample Test Failure with Screenshot](https://user-images.githubusercontent.com/2284832/50541750-3a5a9f80-0b69-11e9-9a49-1b14615e0d33.png)

`The Screenshot that is produced will look something like the following:`

<img src="https://user-images.githubusercontent.com/2284832/50541754-4f373300-0b69-11e9-9db8-f5f9e7a4b779.png" width="300">


## Author

[<NAME>](https://github.com/intere) | [Eric's Github Site](https://intere.github.io/)

## License

UITestKit is available under the MIT license. See the [LICENSE file](https://raw.githubusercontent.com/intere/UITestKit/master/LICENSE) for more info.
<file_sep>/Example/UITestKit_ExampleTests/ShapeTableTests.swift
//
//  ShapeTableTests.swift
//  UITestKit_ExampleTests
//
//  Created by <NAME> on 12/29/18.
//  Copyright © 2018 CocoaPods. All rights reserved.
//

@testable import UITestKit_Example
import XCTest

class ShapeTableTests: ExampleBaseUITest {

    override func setUp() {
        super.setUp()
        disableAnimations()
        shouldPauseUI = true
        pauseTimer = 0.3

        openCircleTab()
    }

    override func tearDown() {
        shouldPauseUI = false
        openCircleTab()
        enableAnimations()
        super.tearDown()
    }

    func testShapesTable() {
        openShapeTab()
        XCTAssertTrue(waitForCondition({ self.shapeTableVC != nil }, timeout: 1), topVCScreenshot)
        guard let shapeTableVC = shapeTableVC else {
            return XCTFail("No ShapeTableVC")
        }
        pauseForUIDebug()

        XCTAssertEqual(0, shapeTableVC.tableView.numberOfRows(inSection: 0), "Wrong number of rows")

        guard let rightButton = shapeTableVC.navigationItem.rightBarButtonItem else {
            return XCTFail("No right button or action")
        }

        for index in 0..<Shape.allCases.count {
            shapeTableVC.addShape(rightButton)
            XCTAssertEqual(index+1, shapeTableVC.tableView.numberOfRows(inSection: 0), topVCScreenshot)
            pauseForUIDebug()
        }

        for _ in 0..<5 {
            shapeTableVC.addShape(rightButton)
            XCTAssertEqual(Shape.allCases.count, shapeTableVC.tableView.numberOfRows(inSection: 0), topVCScreenshot)
        }
        pauseForUIDebug()
    }

}
<file_sep>/Example/UITestKit/Models/Shape.swift
//
//  Shape.swift
//  UITestKit_Example
//
//  Created by <NAME> on 12/29/18.
//  Copyright © 2018 CocoaPods. All rights reserved.
//

import Foundation
import UIKit

enum Shape: String, CaseIterable {
    case triangle
    case circle
    case square
    case rhombus
    case star
    case pentagon
    case rectangle
    case ellipse

    var image: UIImage? {
        return UIImage(named: rawValue)
    }

    /// Info about the shape.
    /// **Sources:
    /// - https://www.smartickmethod.com/blog/math/geometry/geometric-plane-shapes/
    /// - Wikipedia
    var description: String {
        switch self {
        case .triangle:
            return "The triangle is a shape that is formed by 3 straight lines that are called sides.  There are different ways of classifying triangles, according to their sides or angles."

        case .circle:
            return "The circle is a shape that can be made by tracing a curve that is always the same distance from a point that we call the center.  The distance around a circle is called the circumference of the circle."

        case .rectangle:
            return "The rectangle is a shape that has 4 sides.  The distinguishing characteristic of a rectangle is that all 4 angles measure 90 degrees."

        case .rhombus:
            return "The rhombus is a shape formed by 4 straight lines. Its 4 sides measure the same length but, unlike the rectangle, any of all 4 angles measure 90 degrees."

        case .square:
            return "The square is a type of rectangle, but also a type of rhombus. It has characteristics of both of these. That is to say, all 4 angles are right angles, and all 4 sides are equal in length."
        case .star:
            return "In geometry, a star polygon is a type of non-convex polygon. Only the regular star polygons have been studied in any depth; star polygons in general appear not to have been formally defined."
        case .pentagon:
            return "In geometry, a pentagon (from the Greek πέντε pente and γωνία gonia, meaning five and angle) is any five-sided polygon or 5-gon. The sum of the internal angles in a simple pentagon is 540°."
        case .ellipse:
            return "In mathematics, an ellipse is a curve in a plane surrounding two focal points such that the sum of the distances to the two focal points is constant for every point on the curve. As such, it is a generalization of a circle, which is a special type of an ellipse having both focal points at the same location. The shape of an ellipse (how \"elongated\" it is) is represented by its eccentricity, which for an ellipse can be any number from 0 (the limiting case of a circle) to arbitrarily close to but less than 1."
        }
    }
}
<file_sep>/Example/UITestKit/ViewControllers/ShapesTableViewController.swift
//
//  ShapesTableViewController.swift
//  UITestKit_Example
//
//  Created by <NAME> on 12/29/18.
//  Copyright © 2018 CocoaPods. All rights reserved.
//

import UIKit

class ShapesTableViewController: UITableViewController {

    var shapes = [Shape]()

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.register(ShapeTableViewCell.self, forCellReuseIdentifier: "Shape")

        navigationController?.setNavigationBarHidden(false, animated: false)
        navigationController?.navigationItem.rightBarButtonItem?.target = self
        navigationController?.navigationItem.rightBarButtonItem?.action = #selector(addShape(_:))
    }

    // MARK: - Table view data source

    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return shapes.count
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Shape", for: indexPath)

        guard let shapeCell = cell as? ShapeTableViewCell else {
            return cell
        }
        shapeCell.shape = shapes[indexPath.row]

        return shapeCell
    }

}

// MARK: - User Actions

extension ShapesTableViewController {

    @IBAction
    func addShape(_ source: UIBarButtonItem) {
        guard shapes.count < Shape.allCases.count else {
            return
        }
        shapes.append(Shape.allCases[shapes.count])
        tableView.reloadData()
        tableView.scrollToRow(at: IndexPath(row: shapes.count-1, section: 0), at: .bottom, animated: UIView.areAnimationsEnabled)

        if shapes.count == Shape.allCases.count {
            source.isEnabled = false
        }
    }

}
<file_sep>/Example/UITestKit_ExampleTests/UITestKit_ExampleTests.swift
//
//  UITestKit_ExampleTests.swift
//  UITestKit_ExampleTests
//
//  Created by <NAME> on 11/5/17.
//  Copyright © 2017 CocoaPods. All rights reserved.
//

import SafariServices
import UITestKit
import XCTest

class UITestKit_ExampleTests: ExampleBaseUITest {
    
    override func setUp() {
        super.setUp()
        shouldPauseUI = true
        pauseTimer = 1

        XCTAssertNotNil(myTabBarVC, "We couldn't find the root tab view")
    }
    
    override func tearDown() {
        openCircleTab()
        super.tearDown()
    }
    
}

// MARK: - Circle Tab

extension UITestKit_ExampleTests {

    func testOpenCircleTab() {
        XCTAssertNotNil(openCircleTab(), topVCScreenshot)

        // Pause, long enough to see it
        pauseForUIDebug()
    }

    func testOpenSquareTab() {
        XCTAssertNotNil(openSquareTab(), topVCScreenshot)

        // Pause, long enough to see it
        pauseForUIDebug()
    }

}

// MARK: - Safari Tab (Modal VC)

extension UITestKit_ExampleTests {

    func testOpenSafariVC() {

        topVC?.present(SFSafariViewController(url: URL(string: "https://www.google.com")!), animated: true, completion: nil)

        XCTAssertTrue(waitForCondition({ self.topVC is SFSafariViewController }, timeout: 1), topVCScreenshot)
        pauseForUIDebug()

        (topVC as? SFSafariViewController)?.dismiss(animated: true, completion: nil)

        XCTAssertTrue(waitForCondition({ !(self.topVC is SFSafariViewController) }, timeout: 1), "The SFSafariVC didn't dismiss")

    }

}
