         }
     }
 
-    open class func snapshot(_ name: String, waitForLoadingIndicator: Bool = true) {
-        if waitForLoadingIndicator {
-            waitForLoadingIndicatorToDisappear()
-        }
-
-        print("snapshot: \(name)") // more information about this, check out https://github.com/fastlane/fastlane/tree/master/snapshot#how-does-it-work
+    open class func snapshot(_ name: String, timeWaitingForIdle timeout: TimeInterval = 20) {
+        if timeout > 0 {
+            waitForLoadingIndicatorToDisappear(within: timeout)
+        }
+
+        print("snapshot: \(name)") // more information about this, check out https://docs.fastlane.tools/actions/snapshot/#how-does-it-work
 
         sleep(1) // Waiting for the animation to be finished (kind of)
 
-        #if os(tvOS)
-            XCUIApplication().childrenMatchingType(.Browser).count
-        #elseif os(OSX)
+        #if os(OSX)
             XCUIApplication().typeKey(XCUIKeyboardKeySecondaryFn, modifierFlags: [])
         #else
-            XCUIDevice.shared().orientation = .unknown
+            
+            guard let app = self.app else {
+                print("XCUIApplication is not set. Please call setupSnapshot(app) before snapshot().")
+                return
+            }
+            
+            guard let window = app.windows.allElementsBoundByIndex.first(where: { $0.frame.isEmpty == false }) else {
+                print("Couldn't find an element window in XCUIApplication with a non-empty frame.")
+                return
+            }
+
+            let screenshot = window.screenshot()
+            guard let simulator = ProcessInfo().environment["SIMULATOR_DEVICE_NAME"], let screenshotsDir = screenshotsDirectory else { return }
+            let path = screenshotsDir.appendingPathComponent("\(simulator)-\(name).png")
+            do {
+                try screenshot.pngRepresentation.write(to: path)
+            } catch let error {
+                print("Problem writing screenshot: \(name) to \(path)")
+                print(error)
+            }
         #endif
     }
 
-    class func waitForLoadingIndicatorToDisappear() {
+    class func waitForLoadingIndicatorToDisappear(within timeout: TimeInterval) {
         #if os(tvOS)
             return
         #endif
 
-        let query = XCUIApplication().statusBars.children(matching: .other).element(boundBy: 1).children(matching: .other)
-
-        while (0..<query.count).map({ query.element(boundBy: $0) }).contains(where: { $0.isLoadingIndicator }) {
-            sleep(1)
-            print("Waiting for loading indicator to disappear...")
-        }
-    }
-
-    class func pathPrefix() -> URL? {
+        let networkLoadingIndicator = XCUIApplication().otherElements.deviceStatusBars.networkLoadingIndicators.element
+        let networkLoadingIndicatorDisappeared = XCTNSPredicateExpectation(predicate: NSPredicate(format: "exists == false"), object: networkLoadingIndicator)
+        _ = XCTWaiter.wait(for: [networkLoadingIndicatorDisappeared], timeout: timeout)
+    }
+
+    class func pathPrefix() throws -> URL? {
         let homeDir: URL
-        //on OSX config is stored in /Users/<username>/Library
-        //and on iOS/tvOS/WatchOS it's in simulator's home dir
+        // on OSX config is stored in /Users/<username>/Library
+        // and on iOS/tvOS/WatchOS it's in simulator's home dir
         #if os(OSX)
             guard let user = ProcessInfo().environment["USER"] else {
-                print("Couldn't find Snapshot configuration files - can't detect current user ")
-                return nil
-            }
-
-            guard let usersDir =  FileManager.default.urls(for: .userDirectory, in: .localDomainMask).first else {
-                print("Couldn't find Snapshot configuration files - can't detect `Users` dir")
-                return nil
+                throw SnapshotError.cannotDetectUser
+            }
+
+            guard let usersDir = FileManager.default.urls(for: .userDirectory, in: .localDomainMask).first else {
+                throw SnapshotError.cannotFindHomeDirectory
             }
 
             homeDir = usersDir.appendingPathComponent(user)
         #else
-            guard let simulatorHostHome = ProcessInfo().environment["SIMULATOR_HOST_HOME"] else {
-                print("Couldn't find simulator home location. Please, check SIMULATOR_HOST_HOME env variable.")
-                return nil
-            }
-            guard let homeDirUrl = URL(string: simulatorHostHome) else {
-                print("Can't prepare environment. Simulator home location is inaccessible. Does \(simulatorHostHome) exist?")
-                return nil
-            }
-            homeDir = URL(fileURLWithPath: homeDirUrl.path)
+            #if arch(i386) || arch(x86_64)
+                guard let simulatorHostHome = ProcessInfo().environment["SIMULATOR_HOST_HOME"] else {
+                    throw SnapshotError.cannotFindSimulatorHomeDirectory
+                }
+                guard let homeDirUrl = URL(string: simulatorHostHome) else {
+                    throw SnapshotError.cannotAccessSimulatorHomeDirectory(simulatorHostHome)
+                }
+                homeDir = URL(fileURLWithPath: homeDirUrl.path)
+            #else
+                throw SnapshotError.cannotRunOnPhysicalDevice
+            #endif
         #endif
         return homeDir.appendingPathComponent("Library/Caches/tools.fastlane")
     }
 }
 
-extension XCUIElement {
-    var isLoadingIndicator: Bool {
-        let whiteListedLoaders = ["GeofenceLocationTrackingOn", "StandardLocationTrackingOn"]
-        if whiteListedLoaders.contains(self.identifier) {
-            return false
-        }
-        return self.frame.size == CGSize(width: 10, height: 20)
+private extension XCUIElementAttributes {
+    var isNetworkLoadingIndicator: Bool {
+        if hasWhiteListedIdentifier { return false }
+
+        let hasOldLoadingIndicatorSize = frame.size == CGSize(width: 10, height: 20)
+        let hasNewLoadingIndicatorSize = frame.size.width.isBetween(46, and: 47) && frame.size.height.isBetween(2, and: 3)
+
+        return hasOldLoadingIndicatorSize || hasNewLoadingIndicatorSize
+    }
+
+    var hasWhiteListedIdentifier: Bool {
+        let whiteListedIdentifiers = ["GeofenceLocationTrackingOn", "StandardLocationTrackingOn"]
+
+        return whiteListedIdentifiers.contains(identifier)
+    }
+
+    func isStatusBar(_ deviceWidth: CGFloat) -> Bool {
+        if elementType == .statusBar { return true }
+        guard frame.origin == .zero else { return false }
+
+        let oldStatusBarSize = CGSize(width: deviceWidth, height: 20)
+        let newStatusBarSize = CGSize(width: deviceWidth, height: 44)
+
+        return [oldStatusBarSize, newStatusBarSize].contains(frame.size)
+    }
+}
+
+private extension XCUIElementQuery {
+    var networkLoadingIndicators: XCUIElementQuery {
+        let isNetworkLoadingIndicator = NSPredicate { (evaluatedObject, _) in
+            guard let element = evaluatedObject as? XCUIElementAttributes else { return false }
+
+            return element.isNetworkLoadingIndicator
+        }
+
+        return self.containing(isNetworkLoadingIndicator)
+    }
+
+    var deviceStatusBars: XCUIElementQuery {
+        let deviceWidth = XCUIApplication().frame.width
+
+        let isStatusBar = NSPredicate { (evaluatedObject, _) in
+            guard let element = evaluatedObject as? XCUIElementAttributes else { return false }
+
+            return element.isStatusBar(deviceWidth)
+        }
+
+        return self.containing(isStatusBar)
+    }
+}
+
+private extension CGFloat {
+    func isBetween(_ numberA: CGFloat, and numberB: CGFloat) -> Bool {
+        return numberA...numberB ~= self
     }
 }
 
 // Please don't remove the lines below
 // They are used to detect outdated configuration files
-// SnapshotHelperVersion [1.3]
+// SnapshotHelperVersion [1.10]

    }

    class func setLocale(_ app: XCUIApplication) {
        guard let prefix = pathPrefix() else {
            return
        }

        let path = prefix.appendingPathComponent("locale.txt")

        do {
            let trimCharacterSet = CharacterSet.whitespacesAndNewlines

    }

    class func setLaunchArguments(_ app: XCUIApplication) {
        guard let cacheDirectory = self.cacheDirectory else {
            print("CacheDirectory is not set - probably running on a physical device?")
            return
        }
        
        let path = cacheDirectory.appendingPathComponent("snapshot-launch_arguments.txt")
        app.launchArguments += ["-FASTLANE_SNAPSHOT", "YES", "-ui_testing"]

        do {
            let launchArguments = try String(contentsOf: path, encoding: String.Encoding.utf8)
            let regex = try NSRegularExpression(pattern: "(\\\".+?\\\"|\\S+)", options: [])
            let matches = regex.matches(in: launchArguments, options: [], range: NSRange(location: 0, length: launchArguments.count))
            let results = matches.map { result -> String in
                (launchArguments as NSString).substring(with: result.range)
            }
