Migrating an iOS App from the TestFairy SDK
Beta release
The Sauce Mobile Beta SDK is in beta. The artifact names and the API are final; this documentation can still change. Share feedback with your Sauce Labs representative.
Follow these steps to move an iOS app from the TestFairy SDK 1.x to the Sauce Mobile Beta SDK 2.3.0. Read Migrating from the TestFairy SDK first for what changes and why. For the full install reference of the new SDK, see Integrating the iOS SDK.
The module name does not change: you keep import TestFairy in Swift and #import "TestFairy.h" in Objective-C. What changes is the artifact, the entry point, and where crashes go.
Remove every TestFairy SDK 1.x artifact before you add the Sauce Mobile Beta package. Both provide the TestFairy module, so a project that contains both fails with duplicate module and duplicate symbol errors. Never run the TestFairy SDK 1.x beside Backtrace (Sauce Labs Error Reporting) either: both install crash handlers, and a process has one owner of its signal and exception handlers.
1. Remove the TestFairy SDK Artifacts
The TestFairy SDK 1.x was installed through one of the following channels. Remove it from every channel your project uses.
CocoaPods. Remove the pod 'TestFairy' line from your Podfile, then run pod install so that CocoaPods removes the pod from the workspace:
# Podfile: remove this line.
pod 'TestFairy'
cd ios && pod install
Carthage. Remove the binary entry that points to https://app.testfairy.com/sdk/ios/carthage.json from your Cartfile, delete the TestFairySDK.framework that Carthage downloaded into Carthage/Build, and remove it from the Frameworks, Libraries, and Embedded Content section of your target and from any copy-frameworks run script.
# Cartfile: remove this line.
binary "https://app.testfairy.com/sdk/ios/carthage.json"
Manual download. Remove the TestFairySDK.framework that was downloaded from the TestFairy download page from the project navigator and from the Frameworks, Libraries, and Embedded Content section of your target. Also remove a libTestFairy.a static library if one is linked.
Swift Package Manager at a 1.x version. If the project already depends on https://github.com/testfairy/testfairy-ios-sdk-swift-package at a 1.x tag, keep the package. The 1.x package product was named TestFairy, while 2.3.0 names it SauceMobileBeta, so in step 2 you change the version rule and swap the linked product. The module you import stays TestFairy.
Verify. The following command prints nothing. Add the files your project uses:
grep -ri testfairy Podfile Podfile.lock Cartfile Cartfile.resolved 2>/dev/null
Also confirm that no TestFairySDK.framework or libTestFairy.a remains in the project navigator or in the Build Phases of any target.
2. Add the Sauce Mobile Beta Package
The Sauce Mobile Beta SDK for iOS is distributed with Swift Package Manager and requires iOS 13 or later. The CocoaPods pod SauceMobileBeta is not published yet.
| Item | Value |
|---|---|
| Package URL | https://github.com/testfairy/testfairy-ios-sdk-swift-package |
| Package product | SauceMobileBeta |
| Module to import | TestFairy |
| Dependency Rule | Exact Version 2.3.0 |
In Xcode, select the project, open Package Dependencies, click +, enter the package URL, set the Dependency Rule to Exact Version 2.3.0, and add the SauceMobileBeta product to your app target. If the package is already present at a 1.x tag, edit its rule to Exact Version 2.3.0 instead of adding it again, then remove the old TestFairy package product from Frameworks, Libraries, and Embedded Content and add the SauceMobileBeta product in its place. In a Package.swift manifest, change the product name in the .product(name:package:) entry from TestFairy to SauceMobileBeta.
If you declare dependencies in a Package.swift manifest:
dependencies: [
.package(url: "https://github.com/testfairy/testfairy-ios-sdk-swift-package.git", exact: "2.3.0")
],
targets: [
.target(name: "MyApp", dependencies: [
.product(name: "SauceMobileBeta", package: "testfairy-ios-sdk-swift-package")
])
]
Pin the exact version and update it deliberately, so a new SDK release never reaches a build you have not tested.
Verify. Package.resolved lists testfairy-ios-sdk-swift-package at version 2.3.0, and a file that contains import TestFairy compiles.
3. Update the Initialization
Replace begin with beginWithoutCrashHandler. It starts a session, never installs a crash handler, and forces the TFSDKEnableCrashReporterKey begin option to NO even if you pass YES. Replace <sauce-mobile-beta-token> and <server-endpoint> with your values.
Both values are in Organization Settings > SDK Settings on your Mobile App Distribution dashboard, under the organization that owns the app. Only Account Owners and Org Admins can open that page. See Organization Settings.
- SDK App Token: the organization-wide token your app starts sessions with. Regenerating it invalidates the previous token for every build that still carries it.
- Server Endpoint: the URL the SDK sends session data to, in the form
https://<host>/services/. It is specific to your region or private instance, so copy it exactly. If it reads Not configured, contact Sauce Labs support before you ship a build.
Pass the endpoint to setServerEndpoint before beginWithoutCrashHandler. The SDK's built-in default server is no longer in service: an app that skips this call starts no sessions.
- Swift
- Objective-C
SauceMobileBeta, but the module is TestFairy. An existing bridging header with #import "TestFairy.h" also keeps working.import UIKit
import TestFairy
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Before:
// TestFairy.begin("<app-token>")
// After. Initialize Backtrace before this line when it is part of the app.
TestFairy.setServerEndpoint("<server-endpoint>")
TestFairy.beginWithoutCrashHandler("<sauce-mobile-beta-token>")
// the rest of the didFinishLaunchingWithOptions method
return true
}
#import "TestFairy.h"
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Before:
// [TestFairy begin:@"<app-token>"];
// After. Initialize Backtrace before this line when it is part of the app.
[TestFairy setServerEndpoint:@"<server-endpoint>"];
[TestFairy beginWithoutCrashHandler:@"<sauce-mobile-beta-token>"];
// the rest of the didFinishLaunchingWithOptions method
return YES;
}
If you pass begin options, use the options overload of beginWithoutCrashHandler and remove TFSDKEnableCrashReporterKey from the dictionary. The SDK forces it to NO anyway.
Verify. Build and run a debug build. A new session for the build appears in your Sauce Labs Mobile App Distribution dashboard.
4. Delete the Crash Handler Calls and Set Up Backtrace
The crash entry points still exist in the API, but they do nothing in the Sauce Mobile Beta SDK. Delete these calls from your code:
- Swift
- Objective-C
// Delete these calls. They are no-ops in the Sauce Mobile Beta SDK.
TestFairy.installCrashHandler("<app-token>") // does not start a session
TestFairy.enableCrashHandler()
TestFairy.disableCrashHandler()
TestFairy.didLastSessionCrash() // always returns false
// Delete these calls. They are no-ops in the Sauce Mobile Beta SDK.
[TestFairy installCrashHandler:@"<app-token>"]; // does not start a session
[TestFairy enableCrashHandler];
[TestFairy disableCrashHandler];
[TestFairy didLastSessionCrash]; // always returns NO
If your app used installCrashHandler on its own, as a crash handler without a session, that use case is now covered by Backtrace alone.
Crash reporting is provided by Backtrace. Add the Backtrace iOS SDK by following Setting Up Backtrace for iOS, and initialize it before beginWithoutCrashHandler so that it is the only crash owner. New to Backtrace? Start with Getting Started.
Any code that read crash state from the SDK, for example a "the app crashed last time" prompt built on didLastSessionCrash, needs a Backtrace-based replacement or removal.
Verify. The following command prints nothing:
grep -rn "installCrashHandler\|enableCrashHandler\|disableCrashHandler\|didLastSessionCrash" --include="*.swift" --include="*.m" --include="*.mm" .
5. Upload dSYMs to Backtrace
An app on the Sauce Mobile Beta SDK sends no crash reports to Sauce Labs Mobile App Distribution, so there is nothing for it to symbolicate there and its dSYMs no longer go there. Crash symbolication for these apps happens in Backtrace.
- Keep Debug Information Format set to DWARF with dSYM File in your Xcode build settings so that every build produces dSYMs.
- Remove any build phase, script, or CI step that uploads dSYMs to Sauce Labs Mobile App Distribution with your upload API key.
- Upload the dSYMs to Backtrace instead, as described in Upload Debug Symbols and Symbolication.
Verify. A crash report in Backtrace shows your method names instead of addresses.
6. Let the SDK Correlate Sessions
The SDK matches a Backtrace crash report with the session recorded up to the crash. At every session start it generates a new sauce.correlation_id, attaches it to the session, and hands it to Backtrace together with sauce.mobile_beta.session_url, sauce.mobile_beta.session_started, sauce.sdk.coexistence_mode, sauce.release, and sauce.dist. The SDK writes them into BacktraceClient.shared, which it resolves through the Objective-C runtime at every session start, so there is nothing to register. Assign the client before beginWithoutCrashHandler:
- Swift
- Objective-C
BacktraceClient.shared = try? BacktraceClient(configuration: configuration)
TestFairy.setServerEndpoint("<server-endpoint>")
TestFairy.beginWithoutCrashHandler("<sauce-mobile-beta-token>")
// Only for a private client: TestFairy.setBacktraceClient(client)
BacktraceClient.shared = [[BacktraceClient alloc] initWithConfiguration:configuration error:nil];
[TestFairy setServerEndpoint:@"<server-endpoint>"];
[TestFairy beginWithoutCrashHandler:@"<sauce-mobile-beta-token>"];
// Only for a private client: [TestFairy setBacktraceClient:client];
Remove any code that generates a UUID for sauce.correlation_id, sets the SDK-managed keys with setAttribute, or copies the session URL into Backtrace from a session state delegate. setAttribute ignores the reserved keys with a warning, and the SDK overwrites the Backtrace values at the next session start. Keep sauce.environment and mad.distribution_id as your own attributes on both SDKs. To read the current id, call TestFairy.sessionCorrelationId().
The contract is defined once, in Using Sauce Mobile Beta with Backtrace, which has the initialization order and a complete AppDelegate example, and SDK-Managed Attributes.
Do not use the deprecated setCorrelationId or identify methods for the correlation id. They write the user-identity field that setUserId writes. See Identifying Your Users.
Verify. A session's attributes in Sauce Labs Mobile App Distribution show sauce.correlation_id, and a Backtrace report from that session shows the same value. After stop() and a new beginWithoutCrashHandler, sessionCorrelationId returns a different value.
7. Validate Before Release
- The dependency check from step 1 prints nothing, and
Package.resolvedliststestfairy-ios-sdk-swift-packageat2.3.0only. - A clean build succeeds. A duplicate module or duplicate symbol error for
TestFairymeans a TestFairy SDK 1.x artifact is still linked. Go back to step 1. - Run a debug build with both SDKs initialized and force a crash. The process dies as a genuine crash. Relaunch the app: the crash appears in Backtrace only, and the Sauce Mobile Beta SDK starts a new session.
- Submit feedback from the app and confirm that the session shows
sauce.correlation_idin its attributes, with the same value as a Backtrace report from that session. - Confirm that store builds do not contain the Sauce Mobile Beta SDK while Backtrace stays. See Sauce Mobile Beta SDK in Production.
Troubleshooting
The package does not resolve. Check the package URL and set the Dependency Rule to Exact Version 2.3.0; Xcode needs network access to the repository the first time.
Duplicate module or duplicate symbol errors for TestFairy. A TestFairy SDK 1.x artifact is still in the project: a pod 'TestFairy' line, a Carthage or manually added TestFairySDK.framework, or a libTestFairy.a. Remove it, delete the derived data, and rebuild.
TestFairy.h is not found. With a bridging header, try the framework-style import #import "TestFairy/TestFairy.h". In Swift files, use import TestFairy directly.
A crash shows up nowhere. The Sauce Mobile Beta SDK does not report crashes. Make sure Backtrace is initialized before beginWithoutCrashHandler and that its submission URL is correct. See Setting Up Backtrace for iOS.
The crash report shows addresses instead of method names. Upload the dSYMs of that build to Backtrace. See step 5.
didLastSessionCrash returns false after a crash. The SDK returns false by design. Query Backtrace for crash history.