Error: `call FirebaseApp.configure() before using Firestore` - ios

Background Info
I have started developing a backend for an simple App, and I have set up a database class (named DBDelegate) that all the files will communicate with.
In my AppDelegate.swift I have this:
static public var dbDelegate:DBDelegate = DBDelegate()
private func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
FirebaseApp.configure()
return true
}
Its a static public so I can access the dbDelegate from other files.
In my other files, I have the following to help readability: (because it is a class it will pass by reference)
let dbDelegate = AppDelegate.dbDelegate
In my DBDelegate class:
var db = Firestore.firestore()
init() {
FirebaseApp.configure()
}
Building and Running
When I build my code, it builds fine.
On run, the app promptly crashes with SIGABRT.
The error message is:
Terminating app due to uncaught exception 'FIRAppNotConfiguredException', reason: 'Failed to get FirebaseApp instance. Please call FirebaseApp.configure() before using Firestore'
What I have tried
I have tried putting a breakpoint on the init function in the DBDelegate class. It does not reach the breakpoint.
I have tried making the all the dbDelegate variables lazy:
I got a compile error for the one in AppDelegate: lazy must not be used on an already-lazy global
Runtime errors for others: please call FirebaseApp.configure() before using Firestore.
I have tried the following (assigning dbDelegate in didFinishLaunchingWithOptions):
static public var dbDelegate:DBDelegate!
private func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
FirebaseApp.configure()
dbDelegate = DBDelegate()
return true
}
I get Compile error: Static member 'dbDelegate' cannot be used on instance of type 'AppDelegate'
Any help would be great!
Edit: I found a janky solution, see below.

Firstly, I would like to thank #DionzB for suggesting using singletons (which I did). I will reference his/her post in this answer.
Ok, so after some research and playing with breakpoints, I found that my custom class actually executes before the AppDelegate. Knowing such, I created a variable before the following line:
static let shared = FirebaseService()
the name does not matter, because I/you will not call it, and assign it to FirebaseApp.configure()
The FirebaseService class becomes:
class FirebaseService: NSObject {
let constantToNeverTouch = FirebaseApp.configure()
static let shared = FirebaseService()
init() {
}
}
Next, you must make sure that FirebaseApp.configure() is no where else in your code. It should not be in the AppDelegate either. Having multiple FirebaseApp.configure()'s crashes the app.

You can override the AppDelegate init method with FirebaseApp.configure() and make sure it loads before any windows are created.
override init() {
FirebaseApp.configure()
}

It would be better to create a new singleton for Firebase.
class FirebaseService: NSObject {
static let shared = FirebaseService()
init() {
FirebaseApp.configure()
}
}
Then you can access everything via shared like:
FirebaseService.shared.methodName
For configuring in app delegate you would need to call it like:
_ = FirebaseService.shared

Chances are your code are in the wrong order. Make sure your window code are put AFTER the firebaseApp.configure and not before. I was getting the same crash until I simply placed my window creation codes: (window = UIWindow(frame: UIScreen.main.bounds), etc) after the firebaseApp.configure connection .Just swapped their placement:
FirebaseApp.configure()
let db = Firestore.firestore()
let settings = db.settings
settings.areTimestampsInSnapshotsEnabled = true
db.settings = settings
window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = HomeController()
window?.makeKeyAndVisible()

I got the same issue because my pod version it old. So the solution is
Check your pods by using command pod --version
If pod the version is old or not the same, CocoaPods needs to update. Use comment sudo gem install CocoaPods
After it has completed you need to check your pods again use command pod --version make sure your pod version is upgraded
cd to your project directory then use command pod deintegrate
After the command completed then use pod install
Open your project workspace, build & run

Related

How to set and access environment variables (private api keys) from Firebase Cloud Functions inside an iOS app

I use Firebase for my backend, Remote Notifications, and use Google Maps and Places APIs inside my iOS app. Everything works fine but I want to hide my api credentials.
To send a Remote Notification I need the server key from Firebase and to access the Google Maps and Places APIs I need their API Credentials
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
static var fbServerKey = "someReallyLongFirebaseServerKey"
override init() {
super.init()
Messaging.messaging().delegate = self
FirebaseApp.configure()
GMSPlacesClient.provideAPIKey("my_Places_API_Key") // places api credential key
GMSServices.provideAPIKey("my_Maps_API_Key") // maps api credential key
GMSPlacesClient.openSourceLicenseInfo()
GMSServices.openSourceLicenseInfo()
GADMobileAds.sharedInstance().start(completionHandler: nil)
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
}
}
To send a push notification I access the fbServerKey from AppDelegate:
let fbServerKey = AppDelegate.fbServerKey // *** 1. the server key is accessed here and used below ***
var params = [String: Any]() // add keys and values to params
var request = URLRequest(url: URL(string: "https://fcm.googleapis.com/fcm/send")!)
request.httpMethod = "POST"
// *** 2. the server key is used HERE ***
request.setValue("key=\(fbServerKey)", forHTTPHeaderField: "Authorization")
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in ...
I have my Firebase Rules in place but if you look at the above code the fbServerKey and maps/places API credentials are all in plain text.
To avoid that problem I switched to using CloudKit:
// these keys are all set prior to going live and won't be inside the user's applications
NSUbiquitousKeyValueStore().set("someReallyLongFirebaseServerKey", forKey: "fbServerKey")
NSUbiquitousKeyValueStore().set("my_Places_API_Key", forKey: "placesAPIKey")
NSUbiquitousKeyValueStore().set("my_Maps_API_Key", forKey: "mapsAPIKey")
NSUbiquitousKeyValueStore().synchronize()
To retrieve the keys:
static var fbServerKey = NSUbiquitousKeyValueStore()string(forKey: "fbServerKey")
GMSPlacesClient.provideAPIKey(NSUbiquitousKeyValueStore()string(forKey: "placesAPIKey"))
GMSServices.provideAPIKey(NSUbiquitousKeyValueStore()string(forKey: "mapsAPIKey"))
The problem here is if the user isn't connected to iCloud then they won't be able to access the keys.
Now I'm in the process of switching to Firebase Cloud Functions. The thing is I'm not clear how to use it.
1- In terminal I cd to my Xcode project folder and run
// $ firebase init functions *** I initially used this command but DO NOT RUN THIS line >firebase init functions<. Read the first comment below from #DougStevenson for the reason to avoid it
$ npm install -g firebase-tools
$ npm install --save firebase-functions#latest
2- I choose the fb project I want to deploy the cloud functions to
3- I set the keys using the below which I'm lost at because I don't know what someservice.key is nor do I know what someservice.id is nor am i 100% sure where to find my client id (I'm assuming it's the CLIENT_ID from the GoogleService-Info.plist)
$ firebase functions:config:set someservice.key="THE API KEY" someservice.id="THE CLIENT ID
4- Inside the provided functions/index.js folder I add some code to set the code from step3
const functions = require('firebase-functions'); // already present
// don't know what to add here???
5- To deploy the code I run:
$ firebase deploy --only functions
6- Now that the code is in the cloud I can somehow call that code from within in my app to safely access the keys?
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
static var fbServerKey = somehow_Call_The_Firebase_GetFunction_For_This_Server_Key
override init() {
super.init()
Messaging.messaging().delegate = self
FirebaseApp.configure()
GMSPlacesClient.provideAPIKey(somehow_Call_The_Firebase_GetFunction_For_This_Places_Key) // places api credential key
GMSServices.provideAPIKey(somehow_Call_The_Firebase_GetFunction_For_This_Maps_Key) // maps api credential key
GMSPlacesClient.openSourceLicenseInfo()
GMSServices.openSourceLicenseInfo()
GADMobileAds.sharedInstance().start(completionHandler: nil)
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
}
}
I need help with steps 3, 4, and 6

How to clear NSUserDefaults programmatically in XCUITest using Simulator

I've read several answers related to this and they suggest doing one of the following, but these options are not working for me. I have an XCUITest and I'm trying to clear the standard user defaults before before running the rest of my XCUITest. Currently my test app has a button that calls this code. I've also tried calling this code directly from within the XCUITest (I'm not sure if this is expected to work or if it needs to be run from within the app).
NSString *appDomain = [[NSBundle mainBundle] bundleIdentifier];
[[NSUserDefaults standardUserDefaults] removePersistentDomainForName:appDomain];
I've also tried removing each individually:
[[NSUserDefaults standardUserDefaults] removeObjectForKey:#"MyKey1"];
[[NSUserDefaults standardUserDefaults] removeObjectForKey:#"MyKey2"];
I also tried each of the above methods followed by a synchronize call:
[[NSUserDefaults standardUserDefaults] synchronize];
The next time I read #"MyKey1" from the NSUserDefaults its still has the old value and has not been deleted.
Is there any way to remove an object from the NSUserDefaults programmatically when running an XCUITest in the simulator? These are automated tests, so I can't always manually click on "Reset Contents and Settings" in xcode.
Thanks!
There are couple of ways to solve your issues by setting the UserDefaults values before your tests run the app (not after).
Solution #1
Mock UserDefaults values for certain keys using launchArguments:
func testExample() {
let app = XCUIApplication()
app.launchArguments += ["-keepScreenOnKey", "YES"]
app.launch()
}
Note the minus sign before the keepScreenOnKey key. The minus sign indicates that it should take the next launch argument value as a value for that UserDefaults key.
Solution #2 (if solution #1 doesn’t work)
The previous solution might not work if you’re using the SwiftyUserDefaults library. In this case, there is one elegant solution: if the application is running under UI tests, erase all UserDefaults data from the app. How does the app know if it is being run under UI tests? By passing the launch arguments message, like this:
override func setUp() {
super.setUp()
app.launchArguments += ["UI-Testing"]
}
Then, in AppDelegate.swift check if the app is running under UI-Testing and remove all UserDefaults data (like you do):
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
setStateForUITesting()
return true
}
static var isUITestingEnabled: Bool {
get {
return ProcessInfo.processInfo.arguments.contains("UI-Testing")
}
}
private func setStateForUITesting() {
if AppDelegate.isUITestingEnabled {
UserDefaults.standard.removePersistentDomain(forName: Bundle.main.bundleIdentifier!)
}
}
}
Solution #3 (if solution #2 is not enough)
But what if the UI test expects states other than the default state set by solution #2? Bools default to false, Integers to 0 and Strings to "", but what if you need true for the Bool key?
Do something like this:
func testExample() {
app.launchEnvironment["UI-TestingKey_keepScreenOn"] = "YES"
app.launch()
}
And then in AppDelegate.swift file:
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
static let uiTestingKeyPrefix = "UI-TestingKey_"
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
if AppDelegate.isUITestingEnabled {
setUserDefaults()
}
return true
}
static var isUITestingEnabled: Bool {
get {
return ProcessInfo.processInfo.arguments.contains("UI-Testing")
}
}
private func setUserDefaults() {
for (key, value)
in ProcessInfo.processInfo.environment
where key.hasPrefix(AppDelegate.uiTestingKeyPrefix) {
// Truncate "UI-TestingKey_" part
let userDefaultsKey = key.truncateUITestingKey()
switch value {
case "YES":
UserDefaults.standard.set(true, forKey: userDefaultsKey)
case "NO":
UserDefaults.standard.set(false, forKey: userDefaultsKey)
default:
UserDefaults.standard.set(value, forKey: userDefaultsKey)
}
}
}
}
extension String {
func truncateUITestingKey() -> String {
if let range = self.range(of: AppDelegate.uiTestingKeyPrefix) {
let userDefaultsKey = self[range.upperBound...]
return String(userDefaultsKey)
}
return self
}
}
Please note that this example only works for Bool and String keys. If you need more scalability, the switch command should be modified to somehow check if the value is Integer or Double or Any other value, but the general idea is here.
EDIT: It looks like the reason for using solutions #2 and #3 is not valid anymore as of SwiftyUserDefaults version 4.0.0-beta.1 as they've added support for setting values through launch arguments. But, I have to admit that I have not tested SwiftyUserDefaults library from this version onward, so I'll keep both solutions here.
With UI tests, the app runs as a separate process. You would need to call the methods to clear NSUserDefaults from within the app itself.
We have our UI Test pass a resetNSUserDefaults flag to the app when it launches the app. The app then clears the NSUserDefaults early in the launch process.

Firebase app not being configured

This randomly started happening and I cannot get passed it. My app crashes on launch with this in the debug area.
2016-10-29 14:31:57.606 gigMe[2285:73317] Firebase automatic screen reporting is enabled. Call +[FIRAnalytics setScreenName:setScreenClass:] to set the screen name or override the default screen class name. To disable automatic screen reporting, set the flag FirebaseAutomaticScreenReportingEnabled to NO in the Info.plist
2016-10-29 14:31:57.783 gigMe[2285] [Firebase/Core][I-COR000003] The default Firebase app has not yet been configured. Add [FIRApp configure] to your application initialization. Read more: gives google address that i cant post on here
2016-10-29 14:31:57.911 gigMe[2285:73317] * Terminating app due to uncaught exception 'FIRAppNotConfigured', reason: 'Failed to get default FIRDatabase instance. Must call FIRApp.configure() before using FIRDatabase.' * First throw call stack:
I really dont understand this at all because i havent messed with anything that has to do with the database and this is my didFinishLaunchingWithOptions method:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
print("wtf")
FIRApp.configure()
return true
}
im not getting anything printed in the debugger either. anyone know what is going on?
This is not FIRApp.configure() error. You might be declaring a global variable with some class function in any of your class, like
class viewCon : UIViewController{
let ref = FIRDatabase.database().reference() // or a Storage reference
// This might be the error
}
The reason why this happens is because , you are trying to initialise a variable with a class function/property which might not even be configured as of yet. So try this:-
class viewCon : UIViewController{
let ref : FIRDatabaseReference!
// This might be the error or a Storage reference
override func viewDidLoad(){
super.viewDidLoad()
ref = FIRDatabase.database().reference()
}
}
To support above theory, try using breakpoints on let ref = FIRDatabase.database().reference() and FIRApp.configure(), and see which one gets called first. If let ref = FIRDatabase.database().reference() is called first , you are bound to have this error, as ref is trying to access FIRDatabase class, which hasn't been configured yet..

iOS swift You must specify clientID Exception in google integration

Code:
let signIn = GPPSignIn.sharedInstance()
signIn.shouldFetchGooglePlusUser = true
signIn.clientID = "912597493260-qg351fl8olmnmjl8qobos8n6u909jp0o.apps.googleusercontent.com"
signIn.scopes = [kGTLAuthScopePlusLogin];
signIn.trySilentAuthentication();
GIDSignIn.sharedInstance().signInSilently()
signIn.delegate = self
due to uncaught exception 'NSInvalidArgumentException', reason: 'You must specify |clientID| for |GIDSignIn|
I double checked my code.Even i set client-id getting this exception.Where i went wrong?any help will be appreciated.thanks in advance
I was following Google's own guide for adding Sign-In here. I followed it step by step - integrated the google configuration file too. As per the guide, if the configuration file was included, setting the client id manually was not required. Unfortunately, I encountered exactly the same error when I run the app and hit the Sign-In button:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'You must specify |clientID| for |GIDSignIn|'
Solution:
For some reason, clientID was not automatically picked from the configuration file. We should instead configure the GIDSignIn object directly, (using the client ID found in the GoogleService-Info.plist file) in the app delegate's application:didFinishLaunchingWithOptions: method:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Initialize sign-in
var configureError: NSError?
GGLContext.sharedInstance().configureWithError(&configureError)
assert(configureError == nil, "Error configuring Google services: \(configureError)")
GIDSignIn.sharedInstance().clientID = "Cliend id From GoogleService-Info.plist file"
GIDSignIn.sharedInstance().delegate = self
return true
}
Also, if you are using Firebase, you can do it this way too:
GIDSignIn.sharedInstance().clientID = FirebaseApp.app()?.options.clientID
It looks like the auto-generated config file, GoogleService-Info.plist, will include the wrong credentials by default; it includes the Web Client credentials instead of the iOS app credentials.
You need to correct the Client ID and the Reverse Client ID in the GoogleService-Info.plist.
Since these credentials are also used in your app's URLSchemes, you need to correct this there too.
I was also facing the same issue. I followed every step as per the documentation by https://firebase.google.com/docs/auth/ios/google-signin#swift_9 .
At last, I tried adding Client ID manually on my Controller's viewDidLoad and it worked after a long struggle.
Refer the code below. Replace your project specific Client-ID from GoogleService-info.plist in place of ClientID :
class IntroController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
GIDSignIn.sharedInstance().clientID = "*ClientID*"
GIDSignIn.sharedInstance()?.presentingViewController = self
GIDSignIn.sharedInstance().signIn()
}
}
The clientId definitely does get picked up from the .plist file. If it appears not to be, it is likely that your code is attempting to use the sign-in object before it has been properly configured. Set a breakpoint on your configureWithError line, and make sure that it gets hit before any attempt to set a delegate, perform silent sign-in, etc.
Looks like the sign in method has now been updated by google, I was implementing the Google Calendar for iOS app and I found the following code for Sign In:
func applicationDidFinishLaunching(_ application: UIApplication) {
// Initialize sign-in
var configureError: NSError?
GGLContext.sharedInstance().configureWithError(&configureError)
assert(configureError == nil, "Error configuring Google services: \(configureError!)")
}
in their document which gave the same error:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'You must specify |clientID| for |GIDSignIn|'
I took the lines which were inside:
func applicationDidFinishLaunching(_ application: UIApplication)
and put them in this method and sign in worked:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool
Code for refernce:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
// Initialize sign-in
var configureError: NSError?
GGLContext.sharedInstance().configureWithError(&configureError)
assert(configureError == nil, "Error configuring Google services: \(configureError!)")
return true
}
You may need to obtain GoogleService-Info.plist from https://console.firebase.google.com rather than https://console.developers.google.com/.
Using Firebase remember also that you have to call Firebase.configure() function before you set the clientId. Otherwise, it won't work.

How to speed up UI test cases in Xcode?

Since Xcode 7 we have a nice API for UI testing.
Mostly I'm satisfied with it. The only concern is related to the speed.
In the beginning an ordinary UI test case (about 15 actions) ran approximately 25 seconds. Then I mocked networking completely. Now it takes 20 seconds. Considering the fact that the time is taken only by animations and a launch time (1 second or even less), I assume, there must be a way to speed it up.
Try setting this property when your UI tests run:
UIApplication.shared.keyWindow?.layer.speed = 100
Here's how I set it:
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
if ProcessInfo.processInfo.arguments.contains("UITests") {
UIApplication.shared.keyWindow?.layer.speed = 100
}
}
And in my UI tests:
class MyAppUITests: XCTestCase {
// MARK: - SetUp / TearDown
override func setUp() {
super.setUp()
let app = XCUIApplication()
app.launchArguments = ["UITests"]
app.launch()
}
}
There's a few more handy tips in this blog post.
Another possibility is to disable animations at all:
[UIView setAnimationsEnabled:NO];
Swift 3:
UIView.setAnimationsEnabled(false)
Following #Mark answer, the Swift 3 version:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
if ProcessInfo.processInfo.arguments.contains("UITests") {
UIApplication.shared.keyWindow?.layer.speed = 200
}
}
On you ui test file:
override func setUp() {
super.setUp()
// Put setup code here. This method is called before the invocation of each test method in the class.
let app = XCUIApplication()
app.launchArguments = ["UITests"]
app.launch()
Add it in didFinishLaunch
[UIApplication sharedApplication].keyWindow.layer.speed = 2;
The default value is 1, make it 2 to double its speed.
Run them in parallel.
EDIT: This may be outdated since my original answer in 2019, since Xcode allows now testing on multiple simulators within one machine:
If you have only 1 build machine, you can use Bluepill: https://github.com/linkedin/bluepill
EDIT: However, I don't use either of these (bluepill / Xcode), so I'll keep mention of the bluepill in this answer, maybe it has some uses.
If you have multiple machines, you can use Emcee: https://github.com/avito-tech/Emcee (it also works for a single machine setup)
We have 50 hours of UI tests, and Emcee allows us to run them in 1 hour. There are several tricks to make UI tests faster, but it is pointless if you are not running them in parallel. E.g. you can't make your 25 seconds tests run in 0.5 second.
Tricks:
Run XCUIApplication without reinstalling it:
XCUIApplication(
privateWithPath: nil,
bundleID: "your.bundle.id"
)
Note: you should launch app with XCUIApplication().launch() at least once per launching XCUI test runner to install the app. This requires usage of private API.
You can move something to background thread. E.g.
let someDataFromApi = getSomeData() // start request asynchronously and immediately return
launchApp() // this can be few seconds
useInTest(someDataFromApi) // access to data will wait request to finish
You can make the code look like there is no asynchronous things, it would be easier for QA. We want to implement this, but we didn't, so it is just an idea.
Switch to EarlGrey and make tests that lasts few seconds (but they will not be black box).
Open screens via deep links if you have deep links.
Use a lot of private API and other hacks. E.g.: instead of going via UI to Settings app and then reset privacy settings you can call some private API.
Never use long sleeps. Use polling.
Speed up animations. Do not disable them! (we use layer.speed = 100 on every view and we got severe problems with the value of 10000, full code: https://pastebin.com/AnsZmzuQ)
Pass some flags from UI tests to app to skip initial alerts/tutorials/popups. Can save a lot of time. Be sure to have at least 1 test that checks that those alerts work.
Advertisement: most of these is implemented in https://github.com/avito-tech/Mixbox. Disclaimer: I'm the main contributor in this test framework. Its tedious to set up, but at least you can reuse some code if you don't want to reuse whole framework.
I wanted to disable ALL animations during Snapshot testing. I was able to able to achieve this by disabling both Core Animation and UIView animations as below.
Note because my app used storyboards UIApplication.shared.keyWindow was nil at launch so I access the UIWindow by referring to the window property directly.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
if ProcessInfo.processInfo.arguments.contains("SnapshotTests") {
// Disable Core Animations
window?.layer.speed = 0
// Disable UIView animations
UIView.setAnimationsEnabled(false)
}
return true
}
I decrease my UITests time in 30%, follow all steps:
When you run your app, add the argument:
let app = XCUIApplication()
override func setUp() {
super.setUp()
continueAfterFailure = false
app.launchArguments += ["--Reset"]
app.launch()
}
Now, in your AppDelegate add:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
setStateForUITesting()
}
static var isUITestingEnabled: Bool {
get {
return ProcessInfo.processInfo.arguments.contains("--Reset")
}
}
private func setStateForUITesting() {
if AppDelegate.isUITestingEnabled {
// If you need reset your app to clear state
UserDefaults.standard.removePersistentDomain(forName: Bundle.main.bundleIdentifier!)
// To speed up your tests
UIApplication.shared.keyWindow?.layer.speed = 2
UIView.setAnimationsEnabled(false)
}
}
In your code, to verify if is in test mode, you can use:
if AppDelegate.isUITestingEnabled {
print("Test Mode")
}
Additionally, to can wait while the element load I created this extension:
import XCTest
extension XCUIElement {
func tap(wait: Int, test: XCTestCase) {
if !isHittable {
test.expectation(for: NSPredicate(format: "hittable == true"), evaluatedWith: self, handler: nil);
test.waitForExpectations(timeout: TimeInterval(wait), handler: nil)
}
tap()
}
}
Use like this:
app.buttons["start"].tap(wait: 20, test: self)
Note that keyWindow is deprecated as of iOS 13. Specifically,
UIApplication.sharedApplication.keyWindow and self.window.keyWindow within the AppDelegate both return nil. This answer says to loop through UIApplication.shared.windows and find the one with isKeyWindow true, but in my test using ios14, even that return false for my only window. In the end, setting self.window.layer.speed worked for me.

Resources