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]