Skip to content

Commit 1e20102

Browse files
committed
Update fastlane tools
1 parent e32b5d8 commit 1e20102

File tree

2 files changed

+133
-61
lines changed

2 files changed

+133
-61
lines changed

Gemfile.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ GEM
2424
faraday_middleware (0.12.2)
2525
faraday (>= 0.7.4, < 1.0)
2626
fastimage (2.1.0)
27-
fastlane (2.62.1)
27+
fastlane (2.64.0)
2828
CFPropertyList (>= 2.3, < 3.0.0)
2929
addressable (>= 2.3, < 3.0.0)
3030
babosa (>= 1.0.2, < 2.0.0)
@@ -136,4 +136,4 @@ DEPENDENCIES
136136
fastlane (~> 2.37)
137137

138138
BUNDLED WITH
139-
1.15.4
139+
1.16.0

fastlane/SnapshotHelper.swift

Lines changed: 131 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
//
88

99
// -----------------------------------------------------
10-
// IMPORTANT: When modifying this file, make sure to
10+
// IMPORTANT: When modifying this file, make sure to
1111
// increment the version number at the very
1212
// bottom of the file to notify users about
1313
// the new SnapshotHelper.swift
@@ -19,33 +19,67 @@ import XCTest
1919
var deviceLanguage = ""
2020
var locale = ""
2121

22-
@available(*, deprecated, message: "use setupSnapshot: instead")
23-
func setLanguage(_ app: XCUIApplication) {
24-
setupSnapshot(app)
25-
}
26-
2722
func setupSnapshot(_ app: XCUIApplication) {
2823
Snapshot.setupSnapshot(app)
2924
}
3025

31-
func snapshot(_ name: String, waitForLoadingIndicator: Bool = true) {
32-
Snapshot.snapshot(name, waitForLoadingIndicator: waitForLoadingIndicator)
26+
func snapshot(_ name: String, waitForLoadingIndicator: Bool) {
27+
if waitForLoadingIndicator {
28+
Snapshot.snapshot(name)
29+
} else {
30+
Snapshot.snapshot(name, timeWaitingForIdle: 0)
31+
}
32+
}
33+
34+
/// - Parameters:
35+
/// - name: The name of the snapshot
36+
/// - timeout: Amount of seconds to wait until the network loading indicator disappears. Pass `0` if you don't want to wait.
37+
func snapshot(_ name: String, timeWaitingForIdle timeout: TimeInterval = 20) {
38+
Snapshot.snapshot(name, timeWaitingForIdle: timeout)
39+
}
40+
41+
enum SnapshotError: Error, CustomDebugStringConvertible {
42+
case cannotDetectUser
43+
case cannotFindHomeDirectory
44+
case cannotFindSimulatorHomeDirectory
45+
case cannotAccessSimulatorHomeDirectory(String)
46+
47+
var debugDescription: String {
48+
switch self {
49+
case .cannotDetectUser:
50+
return "Couldn't find Snapshot configuration files - can't detect current user "
51+
case .cannotFindHomeDirectory:
52+
return "Couldn't find Snapshot configuration files - can't detect `Users` dir"
53+
case .cannotFindSimulatorHomeDirectory:
54+
return "Couldn't find simulator home location. Please, check SIMULATOR_HOST_HOME env variable."
55+
case .cannotAccessSimulatorHomeDirectory(let simulatorHostHome):
56+
return "Can't prepare environment. Simulator home location is inaccessible. Does \(simulatorHostHome) exist?"
57+
}
58+
}
3359
}
3460

3561
open class Snapshot: NSObject {
62+
static var app: XCUIApplication!
63+
static var cacheDirectory: URL!
64+
static var screenshotsDirectory: URL? {
65+
return cacheDirectory.appendingPathComponent("screenshots", isDirectory: true)
66+
}
3667

3768
open class func setupSnapshot(_ app: XCUIApplication) {
38-
setLanguage(app)
39-
setLocale(app)
40-
setLaunchArguments(app)
69+
do {
70+
let cacheDir = try pathPrefix()
71+
Snapshot.cacheDirectory = cacheDir
72+
Snapshot.app = app
73+
setLanguage(app)
74+
setLocale(app)
75+
setLaunchArguments(app)
76+
} catch let error {
77+
print(error)
78+
}
4179
}
4280

4381
class func setLanguage(_ app: XCUIApplication) {
44-
guard let prefix = pathPrefix() else {
45-
return
46-
}
47-
48-
let path = prefix.appendingPathComponent("language.txt")
82+
let path = cacheDirectory.appendingPathComponent("language.txt")
4983

5084
do {
5185
let trimCharacterSet = CharacterSet.whitespacesAndNewlines
@@ -57,11 +91,7 @@ open class Snapshot: NSObject {
5791
}
5892

5993
class func setLocale(_ app: XCUIApplication) {
60-
guard let prefix = pathPrefix() else {
61-
return
62-
}
63-
64-
let path = prefix.appendingPathComponent("locale.txt")
94+
let path = cacheDirectory.appendingPathComponent("locale.txt")
6595

6696
do {
6797
let trimCharacterSet = CharacterSet.whitespacesAndNewlines
@@ -76,17 +106,13 @@ open class Snapshot: NSObject {
76106
}
77107

78108
class func setLaunchArguments(_ app: XCUIApplication) {
79-
guard let prefix = pathPrefix() else {
80-
return
81-
}
82-
83-
let path = prefix.appendingPathComponent("snapshot-launch_arguments.txt")
109+
let path = cacheDirectory.appendingPathComponent("snapshot-launch_arguments.txt")
84110
app.launchArguments += ["-FASTLANE_SNAPSHOT", "YES", "-ui_testing"]
85111

86112
do {
87113
let launchArguments = try String(contentsOf: path, encoding: String.Encoding.utf8)
88114
let regex = try NSRegularExpression(pattern: "(\\\".+?\\\"|\\S+)", options: [])
89-
let matches = regex.matches(in: launchArguments, options: [], range: NSRange(location:0, length:launchArguments.characters.count))
115+
let matches = regex.matches(in: launchArguments, options: [], range: NSRange(location: 0, length: launchArguments.count))
90116
let results = matches.map { result -> String in
91117
(launchArguments as NSString).substring(with: result.range)
92118
}
@@ -96,78 +122,124 @@ open class Snapshot: NSObject {
96122
}
97123
}
98124

99-
open class func snapshot(_ name: String, waitForLoadingIndicator: Bool = true) {
100-
if waitForLoadingIndicator {
101-
waitForLoadingIndicatorToDisappear()
125+
open class func snapshot(_ name: String, timeWaitingForIdle timeout: TimeInterval = 20) {
126+
if timeout > 0 {
127+
waitForLoadingIndicatorToDisappear(within: timeout)
102128
}
103129

104130
print("snapshot: \(name)") // more information about this, check out https://github.com/fastlane/fastlane/tree/master/snapshot#how-does-it-work
105131

106132
sleep(1) // Waiting for the animation to be finished (kind of)
107133

108-
#if os(tvOS)
109-
XCUIApplication().childrenMatchingType(.Browser).count
110-
#elseif os(OSX)
134+
#if os(OSX)
111135
XCUIApplication().typeKey(XCUIKeyboardKeySecondaryFn, modifierFlags: [])
112136
#else
113-
XCUIDevice.shared().orientation = .unknown
137+
let screenshot = app.windows.firstMatch.screenshot()
138+
guard let simulator = ProcessInfo().environment["SIMULATOR_DEVICE_NAME"], let screenshotsDir = screenshotsDirectory else { return }
139+
let path = screenshotsDir.appendingPathComponent("\(simulator)-\(name).png")
140+
do {
141+
try screenshot.pngRepresentation.write(to: path)
142+
} catch let error {
143+
print("Problem writing screenshot: \(name) to \(path)")
144+
print(error)
145+
}
114146
#endif
115147
}
116148

117-
class func waitForLoadingIndicatorToDisappear() {
149+
class func waitForLoadingIndicatorToDisappear(within timeout: TimeInterval) {
118150
#if os(tvOS)
119151
return
120152
#endif
121153

122-
let query = XCUIApplication().statusBars.children(matching: .other).element(boundBy: 1).children(matching: .other)
123-
124-
while (0..<query.count).map({ query.element(boundBy: $0) }).contains(where: { $0.isLoadingIndicator }) {
125-
sleep(1)
126-
print("Waiting for loading indicator to disappear...")
127-
}
154+
let networkLoadingIndicator = XCUIApplication().otherElements.deviceStatusBars.networkLoadingIndicators.element
155+
let networkLoadingIndicatorDisappeared = XCTNSPredicateExpectation(predicate: NSPredicate(format: "exists == false"), object: networkLoadingIndicator)
156+
_ = XCTWaiter.wait(for: [networkLoadingIndicatorDisappeared], timeout: timeout)
128157
}
129158

130-
class func pathPrefix() -> URL? {
159+
class func pathPrefix() throws -> URL? {
131160
let homeDir: URL
132-
//on OSX config is stored in /Users/<username>/Library
133-
//and on iOS/tvOS/WatchOS it's in simulator's home dir
161+
// on OSX config is stored in /Users/<username>/Library
162+
// and on iOS/tvOS/WatchOS it's in simulator's home dir
134163
#if os(OSX)
135164
guard let user = ProcessInfo().environment["USER"] else {
136-
print("Couldn't find Snapshot configuration files - can't detect current user ")
137-
return nil
165+
throw SnapshotError.cannotDetectUser
138166
}
139167

140168
guard let usersDir = FileManager.default.urls(for: .userDirectory, in: .localDomainMask).first else {
141-
print("Couldn't find Snapshot configuration files - can't detect `Users` dir")
142-
return nil
169+
throw SnapshotError.cannotFindHomeDirectory
143170
}
144171

145172
homeDir = usersDir.appendingPathComponent(user)
146173
#else
147174
guard let simulatorHostHome = ProcessInfo().environment["SIMULATOR_HOST_HOME"] else {
148-
print("Couldn't find simulator home location. Please, check SIMULATOR_HOST_HOME env variable.")
149-
return nil
175+
throw SnapshotError.cannotFindSimulatorHomeDirectory
150176
}
151177
guard let homeDirUrl = URL(string: simulatorHostHome) else {
152-
print("Can't prepare environment. Simulator home location is inaccessible. Does \(simulatorHostHome) exist?")
153-
return nil
178+
throw SnapshotError.cannotAccessSimulatorHomeDirectory(simulatorHostHome)
154179
}
155180
homeDir = URL(fileURLWithPath: homeDirUrl.path)
156181
#endif
157182
return homeDir.appendingPathComponent("Library/Caches/tools.fastlane")
158183
}
159184
}
160185

161-
extension XCUIElement {
162-
var isLoadingIndicator: Bool {
163-
let whiteListedLoaders = ["GeofenceLocationTrackingOn", "StandardLocationTrackingOn"]
164-
if whiteListedLoaders.contains(self.identifier) {
165-
return false
186+
private extension XCUIElementAttributes {
187+
var isNetworkLoadingIndicator: Bool {
188+
if hasWhiteListedIdentifier { return false }
189+
190+
let hasOldLoadingIndicatorSize = frame.size == CGSize(width: 10, height: 20)
191+
let hasNewLoadingIndicatorSize = frame.size.width.isBetween(46, and: 47) && frame.size.height.isBetween(2, and: 3)
192+
193+
return hasOldLoadingIndicatorSize || hasNewLoadingIndicatorSize
194+
}
195+
196+
var hasWhiteListedIdentifier: Bool {
197+
let whiteListedIdentifiers = ["GeofenceLocationTrackingOn", "StandardLocationTrackingOn"]
198+
199+
return whiteListedIdentifiers.contains(identifier)
200+
}
201+
202+
func isStatusBar(_ deviceWidth: CGFloat) -> Bool {
203+
if elementType == .statusBar { return true }
204+
guard frame.origin == .zero else { return false }
205+
206+
let oldStatusBarSize = CGSize(width: deviceWidth, height: 20)
207+
let newStatusBarSize = CGSize(width: deviceWidth, height: 44)
208+
209+
return [oldStatusBarSize, newStatusBarSize].contains(frame.size)
210+
}
211+
}
212+
213+
private extension XCUIElementQuery {
214+
var networkLoadingIndicators: XCUIElementQuery {
215+
let isNetworkLoadingIndicator = NSPredicate { (evaluatedObject, _) in
216+
guard let element = evaluatedObject as? XCUIElementAttributes else { return false }
217+
218+
return element.isNetworkLoadingIndicator
219+
}
220+
221+
return self.containing(isNetworkLoadingIndicator)
222+
}
223+
224+
var deviceStatusBars: XCUIElementQuery {
225+
let deviceWidth = XCUIApplication().frame.width
226+
227+
let isStatusBar = NSPredicate { (evaluatedObject, _) in
228+
guard let element = evaluatedObject as? XCUIElementAttributes else { return false }
229+
230+
return element.isStatusBar(deviceWidth)
166231
}
167-
return self.frame.size == CGSize(width: 10, height: 20)
232+
233+
return self.containing(isStatusBar)
234+
}
235+
}
236+
237+
private extension CGFloat {
238+
func isBetween(_ numberA: CGFloat, and numberB: CGFloat) -> Bool {
239+
return numberA...numberB ~= self
168240
}
169241
}
170242

171243
// Please don't remove the lines below
172244
// They are used to detect outdated configuration files
173-
// SnapshotHelperVersion [1.4]
245+
// SnapshotHelperVersion [1.7]

0 commit comments

Comments
 (0)