Warning, /network/kdeconnect-ios/KDE Connect/fastlane/SnapshotHelper.swift is written in an unsupported language. File is not indexed.

0001 //
0002 //  SnapshotHelper.swift
0003 //  Example
0004 //
0005 //  Created by Felix Krause on 10/8/15.
0006 //
0007 
0008 // -----------------------------------------------------
0009 // IMPORTANT: When modifying this file, make sure to
0010 //            increment the version number at the very
0011 //            bottom of the file to notify users about
0012 //            the new SnapshotHelper.swift
0013 // -----------------------------------------------------
0014 
0015 import Foundation
0016 import XCTest
0017 
0018 var deviceLanguage = ""
0019 var locale = ""
0020 
0021 func setupSnapshot(_ app: XCUIApplication, waitForAnimations: Bool = true) {
0022     Snapshot.setupSnapshot(app, waitForAnimations: waitForAnimations)
0023 }
0024 
0025 func snapshot(_ name: String, waitForLoadingIndicator: Bool) {
0026     if waitForLoadingIndicator {
0027         Snapshot.snapshot(name)
0028     } else {
0029         Snapshot.snapshot(name, timeWaitingForIdle: 0)
0030     }
0031 }
0032 
0033 /// - Parameters:
0034 ///   - name: The name of the snapshot
0035 ///   - timeout: Amount of seconds to wait until the network loading indicator disappears. Pass `0` if you don't want to wait.
0036 func snapshot(_ name: String, timeWaitingForIdle timeout: TimeInterval = 20) {
0037     Snapshot.snapshot(name, timeWaitingForIdle: timeout)
0038 }
0039 
0040 enum SnapshotError: Error, CustomDebugStringConvertible {
0041     case cannotFindSimulatorHomeDirectory
0042     case cannotRunOnPhysicalDevice
0043 
0044     var debugDescription: String {
0045         switch self {
0046         case .cannotFindSimulatorHomeDirectory:
0047             return "Couldn't find simulator home location. Please, check SIMULATOR_HOST_HOME env variable."
0048         case .cannotRunOnPhysicalDevice:
0049             return "Can't use Snapshot on a physical device."
0050         }
0051     }
0052 }
0053 
0054 @objcMembers
0055 open class Snapshot: NSObject {
0056     static var app: XCUIApplication?
0057     static var waitForAnimations = true
0058     static var cacheDirectory: URL?
0059     static var screenshotsDirectory: URL? {
0060         return cacheDirectory?.appendingPathComponent("screenshots", isDirectory: true)
0061     }
0062 
0063     open class func setupSnapshot(_ app: XCUIApplication, waitForAnimations: Bool = true) {
0064 
0065         Snapshot.app = app
0066         Snapshot.waitForAnimations = waitForAnimations
0067 
0068         do {
0069             let cacheDir = try getCacheDirectory()
0070             Snapshot.cacheDirectory = cacheDir
0071             setLanguage(app)
0072             setLocale(app)
0073             setLaunchArguments(app)
0074         } catch let error {
0075             NSLog(error.localizedDescription)
0076         }
0077     }
0078 
0079     class func setLanguage(_ app: XCUIApplication) {
0080         guard let cacheDirectory = self.cacheDirectory else {
0081             NSLog("CacheDirectory is not set - probably running on a physical device?")
0082             return
0083         }
0084 
0085         let path = cacheDirectory.appendingPathComponent("language.txt")
0086 
0087         do {
0088             let trimCharacterSet = CharacterSet.whitespacesAndNewlines
0089             deviceLanguage = try String(contentsOf: path, encoding: .utf8).trimmingCharacters(in: trimCharacterSet)
0090             app.launchArguments += ["-AppleLanguages", "(\(deviceLanguage))"]
0091         } catch {
0092             NSLog("Couldn't detect/set language...")
0093         }
0094     }
0095 
0096     class func setLocale(_ app: XCUIApplication) {
0097         guard let cacheDirectory = self.cacheDirectory else {
0098             NSLog("CacheDirectory is not set - probably running on a physical device?")
0099             return
0100         }
0101 
0102         let path = cacheDirectory.appendingPathComponent("locale.txt")
0103 
0104         do {
0105             let trimCharacterSet = CharacterSet.whitespacesAndNewlines
0106             locale = try String(contentsOf: path, encoding: .utf8).trimmingCharacters(in: trimCharacterSet)
0107         } catch {
0108             NSLog("Couldn't detect/set locale...")
0109         }
0110 
0111         if locale.isEmpty && !deviceLanguage.isEmpty {
0112             locale = Locale(identifier: deviceLanguage).identifier
0113         }
0114 
0115         if !locale.isEmpty {
0116             app.launchArguments += ["-AppleLocale", "\"\(locale)\""]
0117         }
0118     }
0119 
0120     class func setLaunchArguments(_ app: XCUIApplication) {
0121         guard let cacheDirectory = self.cacheDirectory else {
0122             NSLog("CacheDirectory is not set - probably running on a physical device?")
0123             return
0124         }
0125 
0126         let path = cacheDirectory.appendingPathComponent("snapshot-launch_arguments.txt")
0127         app.launchArguments += ["-FASTLANE_SNAPSHOT", "YES", "-ui_testing"]
0128 
0129         do {
0130             let launchArguments = try String(contentsOf: path, encoding: String.Encoding.utf8)
0131             let regex = try NSRegularExpression(pattern: "(\\\".+?\\\"|\\S+)", options: [])
0132             let matches = regex.matches(in: launchArguments, options: [], range: NSRange(location: 0, length: launchArguments.count))
0133             let results = matches.map { result -> String in
0134                 (launchArguments as NSString).substring(with: result.range)
0135             }
0136             app.launchArguments += results
0137         } catch {
0138             NSLog("Couldn't detect/set launch_arguments...")
0139         }
0140     }
0141 
0142     open class func snapshot(_ name: String, timeWaitingForIdle timeout: TimeInterval = 20) {
0143         if timeout > 0 {
0144             waitForLoadingIndicatorToDisappear(within: timeout)
0145         }
0146 
0147         NSLog("snapshot: \(name)") // more information about this, check out https://docs.fastlane.tools/actions/snapshot/#how-does-it-work
0148 
0149         if Snapshot.waitForAnimations {
0150             sleep(1) // Waiting for the animation to be finished (kind of)
0151         }
0152 
0153         #if os(OSX)
0154             guard let app = self.app else {
0155                 NSLog("XCUIApplication is not set. Please call setupSnapshot(app) before snapshot().")
0156                 return
0157             }
0158 
0159             app.typeKey(XCUIKeyboardKeySecondaryFn, modifierFlags: [])
0160         #else
0161 
0162             guard self.app != nil else {
0163                 NSLog("XCUIApplication is not set. Please call setupSnapshot(app) before snapshot().")
0164                 return
0165             }
0166 
0167             let screenshot = XCUIScreen.main.screenshot()
0168             #if os(iOS) && !targetEnvironment(macCatalyst)
0169             let image = XCUIDevice.shared.orientation.isLandscape ?  fixLandscapeOrientation(image: screenshot.image) : screenshot.image
0170             #else
0171             let image = screenshot.image
0172             #endif
0173 
0174             guard var simulator = ProcessInfo().environment["SIMULATOR_DEVICE_NAME"], let screenshotsDir = screenshotsDirectory else { return }
0175 
0176             do {
0177                 // The simulator name contains "Clone X of " inside the screenshot file when running parallelized UI Tests on concurrent devices
0178                 let regex = try NSRegularExpression(pattern: "Clone [0-9]+ of ")
0179                 let range = NSRange(location: 0, length: simulator.count)
0180                 simulator = regex.stringByReplacingMatches(in: simulator, range: range, withTemplate: "")
0181 
0182                 let path = screenshotsDir.appendingPathComponent("\(simulator)-\(name).png")
0183                 #if swift(<5.0)
0184                     try UIImagePNGRepresentation(image)?.write(to: path, options: .atomic)
0185                 #else
0186                     try image.pngData()?.write(to: path, options: .atomic)
0187                 #endif
0188             } catch let error {
0189                 NSLog("Problem writing screenshot: \(name) to \(screenshotsDir)/\(simulator)-\(name).png")
0190                 NSLog(error.localizedDescription)
0191             }
0192         #endif
0193     }
0194 
0195     class func fixLandscapeOrientation(image: UIImage) -> UIImage {
0196         #if os(watchOS)
0197             return image
0198         #else
0199             if #available(iOS 10.0, *) {
0200                 let format = UIGraphicsImageRendererFormat()
0201                 format.scale = image.scale
0202                 let renderer = UIGraphicsImageRenderer(size: image.size, format: format)
0203                 return renderer.image { context in
0204                     image.draw(in: CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height))
0205                 }
0206             } else {
0207                 return image
0208             }
0209         #endif
0210     }
0211 
0212     class func waitForLoadingIndicatorToDisappear(within timeout: TimeInterval) {
0213         #if os(tvOS)
0214             return
0215         #endif
0216 
0217         guard let app = self.app else {
0218             NSLog("XCUIApplication is not set. Please call setupSnapshot(app) before snapshot().")
0219             return
0220         }
0221 
0222         let networkLoadingIndicator = app.otherElements.deviceStatusBars.networkLoadingIndicators.element
0223         let networkLoadingIndicatorDisappeared = XCTNSPredicateExpectation(predicate: NSPredicate(format: "exists == false"), object: networkLoadingIndicator)
0224         _ = XCTWaiter.wait(for: [networkLoadingIndicatorDisappeared], timeout: timeout)
0225     }
0226 
0227     class func getCacheDirectory() throws -> URL {
0228         let cachePath = "Library/Caches/tools.fastlane"
0229         // on OSX config is stored in /Users/<username>/Library
0230         // and on iOS/tvOS/WatchOS it's in simulator's home dir
0231         #if os(OSX)
0232             let homeDir = URL(fileURLWithPath: NSHomeDirectory())
0233             return homeDir.appendingPathComponent(cachePath)
0234         #elseif arch(i386) || arch(x86_64) || arch(arm64)
0235             guard let simulatorHostHome = ProcessInfo().environment["SIMULATOR_HOST_HOME"] else {
0236                 throw SnapshotError.cannotFindSimulatorHomeDirectory
0237             }
0238             let homeDir = URL(fileURLWithPath: simulatorHostHome)
0239             return homeDir.appendingPathComponent(cachePath)
0240         #else
0241             throw SnapshotError.cannotRunOnPhysicalDevice
0242         #endif
0243     }
0244 }
0245 
0246 private extension XCUIElementAttributes {
0247     var isNetworkLoadingIndicator: Bool {
0248         if hasAllowListedIdentifier { return false }
0249 
0250         let hasOldLoadingIndicatorSize = frame.size == CGSize(width: 10, height: 20)
0251         let hasNewLoadingIndicatorSize = frame.size.width.isBetween(46, and: 47) && frame.size.height.isBetween(2, and: 3)
0252 
0253         return hasOldLoadingIndicatorSize || hasNewLoadingIndicatorSize
0254     }
0255 
0256     var hasAllowListedIdentifier: Bool {
0257         let allowListedIdentifiers = ["GeofenceLocationTrackingOn", "StandardLocationTrackingOn"]
0258 
0259         return allowListedIdentifiers.contains(identifier)
0260     }
0261 
0262     func isStatusBar(_ deviceWidth: CGFloat) -> Bool {
0263         if elementType == .statusBar { return true }
0264         guard frame.origin == .zero else { return false }
0265 
0266         let oldStatusBarSize = CGSize(width: deviceWidth, height: 20)
0267         let newStatusBarSize = CGSize(width: deviceWidth, height: 44)
0268 
0269         return [oldStatusBarSize, newStatusBarSize].contains(frame.size)
0270     }
0271 }
0272 
0273 private extension XCUIElementQuery {
0274     var networkLoadingIndicators: XCUIElementQuery {
0275         let isNetworkLoadingIndicator = NSPredicate { (evaluatedObject, _) in
0276             guard let element = evaluatedObject as? XCUIElementAttributes else { return false }
0277 
0278             return element.isNetworkLoadingIndicator
0279         }
0280 
0281         return self.containing(isNetworkLoadingIndicator)
0282     }
0283 
0284     var deviceStatusBars: XCUIElementQuery {
0285         guard let app = Snapshot.app else {
0286             fatalError("XCUIApplication is not set. Please call setupSnapshot(app) before snapshot().")
0287         }
0288 
0289         let deviceWidth = app.windows.firstMatch.frame.width
0290 
0291         let isStatusBar = NSPredicate { (evaluatedObject, _) in
0292             guard let element = evaluatedObject as? XCUIElementAttributes else { return false }
0293 
0294             return element.isStatusBar(deviceWidth)
0295         }
0296 
0297         return self.containing(isStatusBar)
0298     }
0299 }
0300 
0301 private extension CGFloat {
0302     func isBetween(_ numberA: CGFloat, and numberB: CGFloat) -> Bool {
0303         return numberA...numberB ~= self
0304     }
0305 }
0306 
0307 // Please don't remove the lines below
0308 // They are used to detect outdated configuration files
0309 // SnapshotHelperVersion [1.29]