7
7
//
8
8
9
9
// -----------------------------------------------------
10
- // IMPORTANT: When modifying this file, make sure to
10
+ // IMPORTANT: When modifying this file, make sure to
11
11
// increment the version number at the very
12
12
// bottom of the file to notify users about
13
13
// the new SnapshotHelper.swift
@@ -19,33 +19,67 @@ import XCTest
19
19
var deviceLanguage = " "
20
20
var locale = " "
21
21
22
- @available ( * , deprecated, message: " use setupSnapshot: instead " )
23
- func setLanguage( _ app: XCUIApplication ) {
24
- setupSnapshot ( app)
25
- }
26
-
27
22
func setupSnapshot( _ app: XCUIApplication ) {
28
23
Snapshot . setupSnapshot ( app)
29
24
}
30
25
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
+ }
33
59
}
34
60
35
61
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
+ }
36
67
37
68
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
+ }
41
79
}
42
80
43
81
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 " )
49
83
50
84
do {
51
85
let trimCharacterSet = CharacterSet . whitespacesAndNewlines
@@ -57,11 +91,7 @@ open class Snapshot: NSObject {
57
91
}
58
92
59
93
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 " )
65
95
66
96
do {
67
97
let trimCharacterSet = CharacterSet . whitespacesAndNewlines
@@ -76,17 +106,13 @@ open class Snapshot: NSObject {
76
106
}
77
107
78
108
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 " )
84
110
app. launchArguments += [ " -FASTLANE_SNAPSHOT " , " YES " , " -ui_testing " ]
85
111
86
112
do {
87
113
let launchArguments = try String ( contentsOf: path, encoding: String . Encoding. utf8)
88
114
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) )
90
116
let results = matches. map { result -> String in
91
117
( launchArguments as NSString ) . substring ( with: result. range)
92
118
}
@@ -96,78 +122,124 @@ open class Snapshot: NSObject {
96
122
}
97
123
}
98
124
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 )
102
128
}
103
129
104
130
print ( " snapshot: \( name) " ) // more information about this, check out https://github.com/fastlane/fastlane/tree/master/snapshot#how-does-it-work
105
131
106
132
sleep ( 1 ) // Waiting for the animation to be finished (kind of)
107
133
108
- #if os(tvOS)
109
- XCUIApplication ( ) . childrenMatchingType ( . Browser) . count
110
- #elseif os(OSX)
134
+ #if os(OSX)
111
135
XCUIApplication ( ) . typeKey ( XCUIKeyboardKeySecondaryFn, modifierFlags: [ ] )
112
136
#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
+ }
114
146
#endif
115
147
}
116
148
117
- class func waitForLoadingIndicatorToDisappear( ) {
149
+ class func waitForLoadingIndicatorToDisappear( within timeout : TimeInterval ) {
118
150
#if os(tvOS)
119
151
return
120
152
#endif
121
153
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)
128
157
}
129
158
130
- class func pathPrefix( ) -> URL ? {
159
+ class func pathPrefix( ) throws -> URL ? {
131
160
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
134
163
#if os(OSX)
135
164
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
138
166
}
139
167
140
168
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
143
170
}
144
171
145
172
homeDir = usersDir. appendingPathComponent ( user)
146
173
#else
147
174
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
150
176
}
151
177
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)
154
179
}
155
180
homeDir = URL ( fileURLWithPath: homeDirUrl. path)
156
181
#endif
157
182
return homeDir. appendingPathComponent ( " Library/Caches/tools.fastlane " )
158
183
}
159
184
}
160
185
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)
166
231
}
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
168
240
}
169
241
}
170
242
171
243
// Please don't remove the lines below
172
244
// They are used to detect outdated configuration files
173
- // SnapshotHelperVersion [1.4 ]
245
+ // SnapshotHelperVersion [1.7 ]
0 commit comments