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]