diff --git a/.vale.ini b/.vale.ini index fefd7ceba4..de6efa0e29 100644 --- a/.vale.ini +++ b/.vale.ini @@ -3,9 +3,13 @@ StylesPath = styles # suggestion, warning, or error MinAlertLevel = suggestion +# Vale lints front matter values (scope frontmatter.). `id` is the page's URL slug, not prose, so skip it. +# Setting IgnoredScopes replaces Vale's default list (code, tt), so those are repeated here. +IgnoredScopes = code, tt, frontmatter.id + [*.md] BasedOnStyles = sauce -BlockIgnores = ```[a-z]*[\s\S]*?\n``` +# Fenced code is skipped by Vale's Markdown parser; a BlockIgnores regex for it made Vale lint inside code blocks. TokenIgnores = \(\/.*?\), \ {%.*?%}, \ {{.*?}}, \ diff --git a/docs/app-distribution/sdk/adding-tf-sdk.md b/docs/app-distribution/sdk/adding-tf-sdk.md new file mode 100644 index 0000000000..d2bc0ad43a --- /dev/null +++ b/docs/app-distribution/sdk/adding-tf-sdk.md @@ -0,0 +1,230 @@ +--- +id: adding-tf-sdk +title: Adding the Sauce Mobile Beta SDK to Your App +sidebar_label: Adding the Sauce Mobile Beta SDK +description: Add the Sauce Mobile Beta SDK to your Android, iOS, or React Native app to record tester sessions, collect in-app feedback, and send remote logs to Mobile App Distribution. +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +

Beta release

+ +:::info Beta release +The Sauce Mobile Beta SDK is in beta. The current release candidates are 2.2.0-rc for iOS and Android and 3.0.0-rc for React Native. + +- Final: the artifact names and the API. +- Can still change before general availability: the version numbers, the iOS package URL, and this documentation. + +Share feedback with your Sauce Labs representative. +::: + +The Sauce Mobile Beta SDK helps you understand how testers use your app. It records tester sessions, collects in-app feedback and remote logs, and ties every session to a user and a set of attributes, so you can find and inspect sessions on the Sauce Labs Mobile App Distribution dashboard. + +SDK features include: + +- Recording sessions, including video of how testers interact with your app. +- Collecting in-app feedback from testers. See [User Feedback](/app-distribution/sdk/user-feedback). +- Sending logs to the Sauce Labs Mobile App Distribution dashboard for later inspection. See [Remote Logging](/app-distribution/sdk/remote-logging). +- Identifying users so you can find their sessions. See [Identifying Your Users](/app-distribution/sdk/identifying-users). +- Tagging sessions with attributes for searching and custom reports. See [Session Attributes](/app-distribution/sdk/session-attributes). +- Prompting testers to install a newer build that you mark for auto update. See [App Updates](/app-distribution/sdk/app-updates). + +The runtime API is the same on every platform. Import the `TestFairy` module on iOS, `com.testfairy.TestFairy` on Android, and the `TestFairy` default export in React Native. + +## Sauce Mobile Beta and Backtrace + +The SDK and Backtrace (Sauce Labs Error Reporting) cover two different parts of a tester's experience. The SDK records what happened during a session: the video, the logs, the feedback, and the attributes you set. Backtrace reports what went wrong when the app crashes: the stack trace, the device state, and the symbolicated frames. The SDK does not install a crash handler. Crash reporting is provided by Backtrace, and the two SDKs are designed to run in the same app. + +Start the SDK with `beginWithoutCrashHandler`. It starts a session and never installs a crash handler, and it forces the `enableCrashReporter` option (`TFSDKEnableCrashReporterKey` on iOS) to `false` even if you pass `true`. `begin` also starts a session without a crash handler. + + + + + +```java +import com.testfairy.TestFairy; + +TestFairy.beginWithoutCrashHandler(getApplicationContext(), ""); +``` + + + + + +```swift +import TestFairy + +TestFairy.beginWithoutCrashHandler("") +``` + + + + + +```js +import TestFairy from '@saucelabs/mobile-beta-react-native'; + +TestFairy.beginWithoutCrashHandler(''); +``` + + + + + +Where to go next: + +- To set up crash reporting, see [Getting Started with Backtrace](/error-reporting/getting-started/). +- To run both SDKs in one app, initialize Backtrace first, then start the SDK with `beginWithoutCrashHandler`, and give both SDKs the same `sauce.correlation_id` attribute so you can join a crash report with its session recording. See [Using Sauce Mobile Beta with Backtrace](/app-distribution/sdk/backtrace-coexistence). +- To verify the setup end to end by forcing a crash, see [Testing Crash Reporting with Backtrace](/app-distribution/sdk/crash-handler-testing). + +## Supported Platforms + +The SDK is available for the following platforms. Follow the instructions for your platform: + +- [Android](/app-distribution/sdk/android/integrating-android): `com.saucelabs.mobilebeta:sauce-mobile-beta-android` +- [iOS](/app-distribution/sdk/ios/integrating-ios): Swift package `SauceMobileBeta` (module `TestFairy`) +- [React Native](/app-distribution/sdk/react-native/react-native) and [Expo](/app-distribution/sdk/react-native/expo) (with prebuild): `@saucelabs/mobile-beta-react-native` + +See [Supported Platforms](/app-distribution/sdk/supported-platforms) for the features available on each platform. + +## Adding Events + +Events are used to provide insights into how testers use your apps. They can help you track when a tester reaches key points in your app, such as visiting the in-app store. + +To add an event to your timeline: + + + + + +```java +TestFairy.addEvent(""); +``` + +Example + +```java +public class MyActivity extends Activity { + private void onPurchaseComplete() { + TestFairy.addEvent("Purchase OK"); + } +} +``` + + + + + +```objectivec +[TestFairy addEvent:@""]; +``` + +Example + +```objectivec +@implementation ViewController +- (void)viewDidLoad { + [super viewDidLoad]; + [TestFairy addEvent:@"Purchase OK"]; + //... +} +// ... +@end +``` + + + + + +```js +TestFairy.addEvent(""); +``` + +Example + +```js +import React, { useEffect } from 'react'; +import TestFairy from '@saucelabs/mobile-beta-react-native'; + +function PurchaseScreen() { + useEffect(() => { + TestFairy.addEvent('Purchase OK'); + }, []); + + // ... +} +``` + + + + + +## Attaching Files to Sessions + +Sauce Labs Mobile App Distribution allows developers to attach files to sessions. As a developer, you can upload up to five files to a given session, with a maximum size of 15MB per file. Files must be local to the device. + +Be sure to check the device logs for any problems uploading files. Only file extensions .jpeg, .jpg, .png, .txt, and .sqlite are supported. + +To attach a file to a session, call the static instance method `attachFile` in the `TestFairy` class: + + + + + +```java +File file = ... +TestFairy.attachFile(file); +``` + +Example + +```java +File file = new File("/path/to/file.txt"); +TestFairy.attachFile(file); +``` + + + + + +```objectivec +NSURL *file = ... +[TestFairy attachFile:file]; +``` + +Example + +```objectivec +NSURL *file = [NSURL fileURLWithPath:@"/path/to/file.txt"]; +[TestFairy attachFile:file]; +``` + + + + + +:::note Already using the TestFairy SDK? +Follow [Migrating from the TestFairy SDK](/app-distribution/sdk/migration/overview) to move your app to the Sauce Mobile Beta SDK. +::: diff --git a/docs/app-distribution/sdk/android/integrating-android.md b/docs/app-distribution/sdk/android/integrating-android.md new file mode 100644 index 0000000000..2372735d9b --- /dev/null +++ b/docs/app-distribution/sdk/android/integrating-android.md @@ -0,0 +1,248 @@ +--- +id: integrating-android +title: Integrating the Android SDK +sidebar_label: Integrating Android SDK +description: Install and start the Sauce Mobile Beta SDK in a Java or Kotlin Android app. +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +

Beta release

+ +:::info Beta release +The Sauce Mobile Beta SDK is in beta. The current release candidates are 2.2.0-rc for iOS and Android and 3.0.0-rc for React Native. + +- Final: the artifact names and the API. +- Can still change before general availability: the version numbers, the iOS package URL, and this documentation. + +Share feedback with your Sauce Labs representative. +::: + +Integrating the Sauce Mobile Beta SDK into your app can help you better understand how your app performs on real devices. It tells you when and how people use your app and provides any metrics you may need to optimize your user experience and code. + +Both Java and Kotlin apps are supported. The Android artifact is `com.saucelabs.mobilebeta:sauce-mobile-beta-android` and the Java package is `com.testfairy`. + +:::note Crash reporting +The SDK does not install a crash handler. Crash reporting is provided by [Backtrace (Sauce Labs Error Reporting)](/error-reporting/platform-integrations/android/setup/), and the two SDKs are designed to run in the same app. If you run both SDKs, initialize Backtrace first and follow [Using Sauce Mobile Beta with Backtrace](/app-distribution/sdk/backtrace-coexistence). +::: + +## Requirements + +- Android API level 16 or later. When you also ship Backtrace, use Backtrace's minimum (`minSdk 21`). +- Gradle with access to `https://maven.testfairy.com`. The SDK is not served from Maven Central or jcenter. + +## Installation + +### 1. Add the Maven Repository + +Add `https://maven.testfairy.com` to `settings.gradle` (projects created with recent versions of Android Studio). The `content` filter is optional. It tells Gradle to look up only the `com.saucelabs.mobilebeta` group in this repository. + + + + + +```groovy +// settings.gradle +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + maven { + url 'https://maven.testfairy.com' + content { includeGroup 'com.saucelabs.mobilebeta' } + } + } +} +``` + + + + + +```kotlin +// settings.gradle.kts +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + maven { + url = uri("https://maven.testfairy.com") + content { includeGroup("com.saucelabs.mobilebeta") } + } + } +} +``` + + + + + +If your project declares repositories in the root `build.gradle` instead of `settings.gradle`, add the repository there: + +```groovy +// PROJECT_ROOT/build.gradle +allprojects { + repositories { + google() + mavenCentral() + maven { url 'https://maven.testfairy.com' } + } +} +``` + +### 2. Add the Dependency + +Add the SDK to your app module's `build.gradle` (for example `app/build.gradle`). Pin the exact version. See [Upgrading](#upgrading). + + + + + +```groovy +dependencies { + implementation "com.saucelabs.mobilebeta:sauce-mobile-beta-android:2.2.0-rc" +} +``` + + + + + +```kotlin +dependencies { + implementation("com.saucelabs.mobilebeta:sauce-mobile-beta-android:2.2.0-rc") +} +``` + + + + + +The SDK provides beta-testing capabilities: session recording, tester feedback, remote logs and [update prompts](/app-distribution/sdk/app-updates). Prefer a beta or debug-only dependency, such as `debugImplementation` or a `betaImplementation` flavor configuration, so that store builds do not ship it. See [Sauce Mobile Beta SDK in Production](/app-distribution/sdk/tf-production). + +### 3. Initialize the SDK + +Initialize the SDK in the `onCreate` method of an `Application` subclass with `beginWithoutCrashHandler`. Replace `` with the app token from your Sauce Labs Mobile App Distribution dashboard. + + + + + +```java +import android.app.Application; +import com.testfairy.TestFairy; + +public class MyApplication extends Application { + + @Override + public void onCreate() { + super.onCreate(); + // If you use Backtrace, initialize it before this line. + TestFairy.beginWithoutCrashHandler(this, ""); + } +} +``` + + + + + +```kotlin +import android.app.Application +import com.testfairy.TestFairy + +class MyApplication : Application() { + + override fun onCreate() { + super.onCreate() + // If you use Backtrace, initialize it before this line. + TestFairy.beginWithoutCrashHandler(this, "") + } +} +``` + + + + + +Register the class in `AndroidManifest.xml`: + +```xml + + +``` + +`beginWithoutCrashHandler` is the recommended entry point: it starts a session, never installs a crash handler, and forces the `enableCrashReporter` option to `false`, even if you pass `true` through the options overload `beginWithoutCrashHandler(context, appToken, options)`. The plain `begin(...)` overloads also start a session without a crash handler. + +When you run both SDKs, give them the same `sauce.correlation_id` session attribute. Both SDKs should carry the same `sauce.correlation_id` session attribute. The order, the shared attributes and a complete `Application` example are in [Using Sauce Mobile Beta with Backtrace](/app-distribution/sdk/backtrace-coexistence). + +## ProGuard and R8 (Optional) + +The AAR bundles its consumer ProGuard rules, so Gradle applies them to your app automatically when minification is enabled. You do not need to add rules for the SDK. + +## Upgrading + +Always pin an exact version, such as `2.2.0-rc`, and update it deliberately. Do not use version wildcards such as `2.+`: they resolve unpredictably for pre-release versions and can pull in a build you have not tested. + +`2.2.0-rc` is a release candidate. The general availability release uses the same coordinates with a plain version number. To upgrade, change the version string in `build.gradle` and rebuild. `2.2.0-rc` is served from `https://maven.testfairy.com` under the coordinate `com.saucelabs.mobilebeta:sauce-mobile-beta-android:2.2.0-rc`. + +## How to Identify Users (Optional) + +The following example identifies users by email address. + +```java +TestFairy.setUserId("john@example.com"); +``` + +Read [Identifying Your Users](/app-distribution/sdk/identifying-users) for more identification options. + +## Additional Permissions (Optional) + +The SDK can record additional insights that require specific permissions. Below is a list of permissions required for each metric: + +### Logs - `android.permission.READ_LOGS` (Optional) + +To automatically upload device logs (logcat) to your account, add the permission `android.permission.READ_LOGS`. +Check the **Log collection** under the **Insights** in Build Settings. You can do it after the app is uploaded or the first session performed. + +### Tracking Battery Usage - `android.permission.BATTERY_STATS` (Optional) + +Add the `android.permission.BATTERY_STATS` permission to automatically upload the battery status to your account. +It tracks the general battery status and when the device is connected or disconnected from a charger. + +### Tracking Phone Signal - `android.permission.READ_PHONE_STATE` (Optional) + +To automatically upload a phone signal to your account, add the `android.permission.READ_PHONE_STATE` permission. +The phone signal graph shows the GSM Signal Strength, with valid values (0-31, 99). 0 equals -113 dBm or less, and 31 equals -51 dBm or more. For more information, read [GSM standard TS 27.007, section 8.5](http://www.etsi.org/deliver/etsi_ts/127000_127099/127007/08.05.00_60/ts_127007v080500p.pdf) + +### Tracking WiFi Signal - `android.permission.ACCESS_WIFI_STATE` (Optional) + +To automatically upload the wifi status to your account, add the `android.permission.ACCESS_WIFI_STATE` permission: it tracks the wifi signal. + +## Troubleshooting + +The SDK is served from `maven.testfairy.com`, not jcenter. If Gradle reports a `Could not GET` error for a `jcenter.bintray.com` URL, or `Could not find com.saucelabs.mobilebeta:sauce-mobile-beta-android:2.2.0-rc`, add the repository as described in [step 1](#1-add-the-maven-repository) of the installation section. diff --git a/docs/app-distribution/sdk/app-updates.md b/docs/app-distribution/sdk/app-updates.md new file mode 100644 index 0000000000..8cfcfc7c99 --- /dev/null +++ b/docs/app-distribution/sdk/app-updates.md @@ -0,0 +1,263 @@ +--- +id: app-updates +title: App Updates +sidebar_label: App Updates +description: Prompt testers running an older build to update when a newer build is marked for auto update, and control that prompt from the Sauce Mobile Beta SDK. +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +

Beta release

+ +Sauce Labs Mobile App Distribution can tell your testers when a newer build of your app is available. When you mark a build for auto update, testers who run an older build see a prompt the next time the Sauce Mobile Beta SDK starts a session, and one tap takes them to the download. No app code is required for this. The sections below explain what the prompt does, how to turn it off for builds that must not update themselves, how your app can react to it, and how to query the update status yourself. + +## How It Works + +1. Upload a new build and turn on **Auto Update** for it, either in the build's settings on the Sauce Labs Mobile App Distribution dashboard or at upload time with the `auto_update` parameter of the [Upload API](/testfairy/api-reference/upload-api/) or the `auto_update` option of the [fastlane plugin](/testfairy/ci-tools/fastlane/). Both accept `on` or `off` and default to `off`. +2. When an older build of the same app starts a session, the SDK asks Sauce Labs Mobile App Distribution whether a newer build is marked for auto update. +3. If there is one, the SDK shows a prompt that names the new version and asks whether to update, with **Yes** and **No**. On Android it reads "New version is available! Would you like to download and install version 2.1.0?". On iOS it reads "New version of My App 2.1.0 is available, would you like to upgrade now?". The SDK localizes the text. + - **Yes** opens the link outside the app. The tester installs the new build from there, the same way they installed the current one. The SDK itself does not download or install anything. + - **No** starts the session as usual. +4. If no newer build is marked for auto update, the session starts without a prompt. + +:::note +On Android, the prompt needs an activity. If the app is in the background when the answer arrives, the SDK shows the prompt when an activity comes to the foreground. If the app is in the foreground without an activity, for example when you start the SDK in `Application.onCreate` before the first activity exists, the SDK skips the prompt for that session start, calls `onAutoUpdateDownloadFailed`, and starts the session without the update. +::: + +## Turning the Prompt Off + +Call `disableAutoUpdate` before `beginWithoutCrashHandler`. The SDK then ignores any newer build for that session, and no prompt is shown. + + + + + +```java +TestFairy.disableAutoUpdate(); +TestFairy.beginWithoutCrashHandler(getApplicationContext(), ""); +``` + + + + + +```objectivec +[TestFairy disableAutoUpdate]; +[TestFairy beginWithoutCrashHandler:@""]; +``` + +Swift + +```swift +TestFairy.disableAutoUpdate() +TestFairy.beginWithoutCrashHandler("") +``` + + + + + +```js +TestFairy.disableAutoUpdate(); +TestFairy.beginWithoutCrashHandler(''); +``` + + + + + +:::caution Store builds +Never let a build that ships through the App Store or Google Play update itself. It violates the [App Store Review Guidelines](https://developer.apple.com/app-store/review/guidelines/) and the [Play Store Developer Distribution Agreement](https://play.google.com/about/developer-distribution-agreement.html). Call `disableAutoUpdate` in those builds, or better, keep the SDK out of them entirely. See [Sauce Mobile Beta SDK in Production](/app-distribution/sdk/tf-production). +::: + +## Handling the Prompt + +The session state listener tells your app what happened, for example so you can log the event. Registering a listener does not replace the SDK prompt. To show your own message instead of the prompt, see [Checking the Distribution Status](#checking-the-distribution-status). Register the listener before `beginWithoutCrashHandler`. + +The table uses the iOS delegate method names. On Android and React Native the same callbacks start with `on`, for example `onAutoUpdateAvailable`, and React Native passes the URL as `{ url }`. + +| Callback | When it is called | +| --- | --- | +| `autoUpdateAvailable(url)` | A newer build is marked for auto update. `url` is the link to that build. | +| `autoUpdateDownloadStarted` | The tester tapped **Yes** and the link was opened. iOS only. The Android callback exists but is not called. | +| `autoUpdateDismissed` | The tester tapped **No**. | +| `autoUpdateDownloadFailed` | The prompt could not be shown or the link could not be opened, for example because no activity was on screen. The session then starts without the update. Android only, including React Native apps running on Android. | +| `noAutoUpdateAvailable` | A session was requested and no newer build is marked for auto update. | + + + + + +```java +import android.util.Log; +import com.testfairy.SessionStateListener; +import com.testfairy.TestFairy; + +TestFairy.addSessionStateListener(new SessionStateListener() { + @Override + public void onAutoUpdateAvailable(String url) { + Log.i("MyApp", "A newer build is available at " + url); + } + + @Override + public void onAutoUpdateDismissed() { + Log.i("MyApp", "The tester declined the update"); + } +}); +TestFairy.beginWithoutCrashHandler(getApplicationContext(), ""); +``` + + + + + +```objectivec +#import "TestFairy.h" + +@interface AppDelegate () +@end + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + [TestFairy setSessionStateDelegate:self]; + [TestFairy beginWithoutCrashHandler:@""]; + return YES; +} + +- (void)autoUpdateAvailable:(NSString *)url { + NSLog(@"A newer build is available at %@", url); +} + +- (void)autoUpdateDismissed { + NSLog(@"The tester declined the update"); +} + +@end +``` + +Swift + +```swift +import TestFairy + +class AppDelegate: UIResponder, UIApplicationDelegate, TestFairySessionStateDelegate { + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + TestFairy.setSessionStateDelegate(self) + TestFairy.beginWithoutCrashHandler("") + return true + } + + func autoUpdateAvailable(_ url: String) { + print("A newer build is available at \(url)") + } + + func autoUpdateDismissed() { + print("The tester declined the update") + } +} +``` + + + + + +```js +import TestFairy from '@saucelabs/mobile-beta-react-native'; + +const subscription = TestFairy.addSessionStateListener({ + onAutoUpdateAvailable: ({ url }) => console.log(`A newer build is available at ${url}`), + onAutoUpdateDismissed: () => console.log('The tester declined the update'), + onAutoUpdateDownloadFailed: () => console.log('The prompt could not be shown'), +}); +TestFairy.beginWithoutCrashHandler(''); + +// Later, to stop receiving the events: +subscription.remove(); +``` + + + + + +## Checking the Distribution Status + +On Android and iOS, `getDistributionStatus` asks Sauce Labs Mobile App Distribution about the running build without starting a session. The answer tells you whether distribution is enabled for this build (a build with the same package name or bundle identifier, version name and build number exists on the dashboard), whether a newer build is marked for auto update, and the link to that build. The link is signed and expires, so request it when you are about to use it instead of storing it. + +To replace the SDK prompt with your own: call `disableAutoUpdate` before `beginWithoutCrashHandler` (see [Turning the Prompt Off](#turning-the-prompt-off)), call `getDistributionStatus`, show your own message, and open the link when the tester agrees. React Native does not expose this call. + + + + + +```java +import com.testfairy.DistributionStatus; +import com.testfairy.DistributionStatusListener; +import com.testfairy.TestFairy; + +TestFairy.getDistributionStatus(getApplicationContext(), "", new DistributionStatusListener() { + @Override + public void onResponse(DistributionStatus status) { + if (status.isAutoUpdateAvailable()) { + String downloadUrl = status.getAutoUpdateDownloadUrl(); + // Show your own update message and open downloadUrl when the tester agrees. + } + } +}); +``` + + + + + +```objectivec +[TestFairy getDistributionStatus:@"" callback:^(NSDictionary *response, NSError *error) { + if (response == nil) { + return; + } + // response[@"status"] is @"enabled" or @"disabled". + NSString *downloadUrl = response[@"autoUpdateDownloadUrl"]; + if (downloadUrl != nil) { + // Show your own update message and open downloadUrl when the tester agrees. + } +}]; +``` + +Swift + +```swift +TestFairy.getDistributionStatus("") { response, error in + guard let response = response else { return } + // response["status"] is "enabled" or "disabled". + if let downloadUrl = response["autoUpdateDownloadUrl"] { + // Show your own update message and open downloadUrl when the tester agrees. + } +} +``` + + + + diff --git a/docs/app-distribution/sdk/backtrace-coexistence.md b/docs/app-distribution/sdk/backtrace-coexistence.md new file mode 100644 index 0000000000..5e49a708b8 --- /dev/null +++ b/docs/app-distribution/sdk/backtrace-coexistence.md @@ -0,0 +1,303 @@ +--- +id: backtrace-coexistence +title: Using Sauce Mobile Beta with Backtrace +sidebar_label: Using with Backtrace +description: Run the Sauce Mobile Beta SDK and Backtrace (Sauce Labs Error Reporting) in one app and match crash reports to session recordings with a shared correlation attribute. +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +

Beta release

+ +:::info Beta release +The Sauce Mobile Beta SDK is in beta. The current release candidates are 2.2.0-rc for iOS and Android and 3.0.0-rc for React Native. + +- Final: the artifact names and the API. +- Can still change before general availability: the version numbers, the iOS package URL, and this documentation. + +Share feedback with your Sauce Labs representative. +::: + +The Sauce Mobile Beta SDK and Backtrace (Sauce Labs Error Reporting) run side by side in one app. This guide describes the contract that makes that possible: Backtrace owns crashes, Sauce Mobile Beta owns beta sessions, and both carry the same correlation attribute so a crash report and its session recording can be matched across the two consoles. + +## Overview + +A process can have only one owner of its signal and exception handlers. The Sauce Mobile Beta SDK never installs one, at any layer and on any platform, so Backtrace is always the sole crash owner. The two SDKs are designed to run in the same app. + +| Product | Owns | +| :------------------------- | :-------------------------------------------------------------------------------------------------------------------------- | +| Backtrace | Native crashes (iOS, Android JVM and NDK), JavaScript errors and unhandled promise rejections (React Native), out-of-memory detection, symbolication | +| Sauce Mobile Beta SDK | Beta sessions and video, tester feedback and screenshots, remote logs and events, session attributes, tester workflow | + +Import the `TestFairy` module on iOS, `com.testfairy.TestFairy` on Android, and the `TestFairy` default export in React Native. On every platform, `beginWithoutCrashHandler` is the entry point for an app that runs Backtrace: it starts a session and never installs a crash handler. + +## Before You Begin + +Install both SDKs. + +1. Install Backtrace for your platform: [iOS](/error-reporting/platform-integrations/ios/setup/), [Android](/error-reporting/platform-integrations/android/setup/) (plus [Native Crash Integration](/error-reporting/platform-integrations/android/native-crash-integration/) if you want NDK crashes), or [React Native](/error-reporting/language-integrations/react-native/). New to Backtrace? Start with [Getting Started](/error-reporting/getting-started/). + - Android: use Backtrace Android SDK 3.7.14 or later, which added `BacktraceClient.addAttribute` (see [Configuring Backtrace for Android](/error-reporting/platform-integrations/android/configuration/)). On 3.7.13 or earlier, use `((BacktraceDatabase) client.database).addAttribute(key, value)` instead. +2. Install the Sauce Mobile Beta SDK: [iOS](/app-distribution/sdk/ios/integrating-ios), [Android](/app-distribution/sdk/android/integrating-android), or [React Native](/app-distribution/sdk/react-native/react-native). +3. On Android, use Backtrace's `minSdk` (21) for the combined app. Sauce Mobile Beta alone supports `minSdk` 16. + +## Initialization Order + +Initialize in this order on every platform: + +1. Generate one lowercase UUID v4 for this launch and build the shared attribute map before either SDK starts. +2. Initialize Backtrace with the shared attributes. Backtrace installs the crash handlers. +3. Register a Sauce Mobile Beta session-state listener that mirrors the session URL back into Backtrace. +4. Copy the shared attributes to Sauce Mobile Beta with `setAttribute`. +5. Start Sauce Mobile Beta with `beginWithoutCrashHandler`. + +The listener is registered before `beginWithoutCrashHandler` because the session URL is assigned asynchronously. Reading it immediately after `begin` can return null. + + + + + +Pass the attribute map to the `BacktraceClient` constructor that also takes `BacktraceDatabaseSettings`. The attributes-only constructor creates a disabled database, and `enableNativeIntegration()` then silently does nothing. Use the `/json` submission URL and keep `enableNativeIntegration()` as the last Backtrace setup call: native annotations are snapshotted there, so values added later reach native reports only through `client.addAttribute` (`backtrace-android` 3.8 and later) or `((BacktraceDatabase) client.database).addAttribute` (3.7.x). + +```kotlin +import android.app.Application +import backtraceio.library.BacktraceClient +import backtraceio.library.BacktraceCredentials +import backtraceio.library.models.BacktraceExceptionHandler +import backtraceio.library.models.database.BacktraceDatabaseSettings +import com.testfairy.SessionStateListener +import com.testfairy.TestFairy +import java.io.File +import java.util.UUID + +class MainApplication : Application() { + private lateinit var backtrace: BacktraceClient + + override fun onCreate() { + super.onCreate() + // 1. One lowercase UUID v4 per launch, before either SDK starts. + val shared = mapOf( + "sauce.correlation_id" to UUID.randomUUID().toString(), + "sauce.sdk.coexistence_mode" to "backtrace_crash_owner", + "sauce.environment" to "beta", + "sauce.release" to "${BuildConfig.APPLICATION_ID}@${BuildConfig.VERSION_NAME}", + "sauce.dist" to BuildConfig.VERSION_CODE.toString(), + ) + + // 2. Backtrace first: sole JVM and native crash owner. + backtrace = BacktraceClient( + applicationContext, + BacktraceCredentials("https://submit.backtrace.io///json"), + BacktraceDatabaseSettings(File(applicationContext.filesDir, "backtrace").absolutePath), + HashMap(shared) + ) + BacktraceExceptionHandler.enable(backtrace) + backtrace.enableNativeIntegration() // keep last + + // 3. Mirror every session URL into Backtrace (overwritten on each start). + TestFairy.addSessionStateListener(object : SessionStateListener() { + override fun onSessionStarted(sessionUrl: String?) { + // backtrace-android 3.7.14+; on 3.7.13 or earlier: (backtrace.database as BacktraceDatabase).addAttribute(key, value) + backtrace.addAttribute("sauce.mobile_beta.session_started", "true") + backtrace.addAttribute("sauce.mobile_beta.session_url", sessionUrl ?: "") + } + override fun onSessionFailed() { + backtrace.addAttribute("sauce.mobile_beta.session_started", "false") + } + }) + + // 4. Same attributes before begin; the SDK keeps them for every session. + shared.forEach { (key, value) -> TestFairy.setAttribute(key, value) } + + // 5. Start the SDK. It never installs a crash handler. + TestFairy.beginWithoutCrashHandler(applicationContext, "") + } +} +``` + + + + + +Make the attributes assignment the very next statement after `try BacktraceClient(configuration:)`. PLCrashReporter's handlers are installed by then, and only that setter writes attributes into native crash reports. A crash before that line carries no correlation id. Assign the full map immediately after init, with `String` values. Later updates such as the session URL go through the same setter, which rewrites the attributes stored for crash reports. Use the `/plcrash` submission URL. + +```swift +import UIKit +import Backtrace +import TestFairy // the SwiftPM product is SauceMobileBeta + +@main +final class AppDelegate: UIResponder, UIApplicationDelegate { + private let sessionObserver = SessionObserver() + + func application(_ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + // 1. One lowercase UUID v4 per launch, before either SDK starts. + let info = Bundle.main.infoDictionary ?? [:] + let shared: [String: String] = [ + "sauce.correlation_id": UUID().uuidString.lowercased(), + "sauce.sdk.coexistence_mode": "backtrace_crash_owner", + "sauce.environment": "beta", + "sauce.release": "\(Bundle.main.bundleIdentifier ?? "")@\(info["CFBundleShortVersionString"] ?? "")", + "sauce.dist": "\(info["CFBundleVersion"] ?? "")", + ] + + // 2. Backtrace first: sole crash owner. + let credentials = BacktraceCredentials( + submissionUrl: URL(string: "https://submit.backtrace.io///plcrash")!) + let configuration = BacktraceClientConfiguration(credentials: credentials) + BacktraceClient.shared = try? BacktraceClient(configuration: configuration) + BacktraceClient.shared?.attributes = shared // full map, next statement after init + + // 3. Mirror every session URL into Backtrace (register once; the SDK retains it). + TestFairy.setSessionStateDelegate(sessionObserver) + + // 4. Same attributes before begin; the SDK keeps them for every session. + for (key, value) in shared { TestFairy.setAttribute(key, withValue: value) } + + // 5. Start the SDK. It never installs a crash handler. + TestFairy.beginWithoutCrashHandler("") + return true + } +} + +private final class SessionObserver: NSObject, TestFairySessionStateDelegate { + func sessionStarted() { + guard let client = BacktraceClient.shared else { return } + var attributes = client.attributes + attributes["sauce.mobile_beta.session_started"] = "true" + attributes["sauce.mobile_beta.session_url"] = TestFairy.sessionUrl() ?? "" + client.attributes = attributes + } + func sessionFailed() { + guard let client = BacktraceClient.shared else { return } + var attributes = client.attributes + attributes["sauce.mobile_beta.session_started"] = "false" + client.attributes = attributes + } +} +``` + + + + + +Pass the shared attributes as `userAttributes` in `BacktraceClient.initialize(...)`. `addSessionStateListener` returns a subscription. Call `remove()` on it if you tear the integration down. + +Install `uuid` and `react-native-get-random-values` (`npm install uuid react-native-get-random-values`). Neither is a dependency of the Sauce Mobile Beta package. Import the polyfill before `uuid`. + +```ts +import 'react-native-get-random-values'; // before uuid on React Native +import { v4 as uuidv4 } from 'uuid'; +import { BacktraceClient } from '@backtrace/react-native'; +import TestFairy from '@saucelabs/mobile-beta-react-native'; + +export function initializeObservability() { + // 1. One lowercase UUID v4 per launch, before either SDK starts. + const shared: Record = { + 'sauce.correlation_id': uuidv4(), + 'sauce.sdk.coexistence_mode': 'backtrace_crash_owner', + 'sauce.environment': 'beta', + 'sauce.release': '@', + 'sauce.dist': '', + }; + + // 2. Backtrace first: sole crash owner (JS errors, native crashes). + const backtrace = BacktraceClient.initialize({ + url: 'https://submit.backtrace.io///json', + userAttributes: shared, + database: { + enable: true, + captureNativeCrashes: true, + createDatabaseDirectory: true, + path: `${BacktraceClient.applicationDataPath}/backtrace`, + }, + }); + + // 3. Mirror every session URL into Backtrace (overwritten on each start). + const subscription = TestFairy.addSessionStateListener({ + onSessionStarted({ sessionUrl }) { + backtrace.addAttribute({ + 'sauce.mobile_beta.session_started': 'true', + 'sauce.mobile_beta.session_url': sessionUrl ?? '', + }); + }, + onSessionFailed() { + backtrace.addAttribute({ 'sauce.mobile_beta.session_started': 'false' }); + }, + }); + + // 4. Same attributes before begin; the SDK keeps them for every session. + for (const [key, value] of Object.entries(shared)) { + TestFairy.setAttribute(key, value); + } + + // 5. Start the SDK. It never installs a crash handler. + TestFairy.beginWithoutCrashHandler(''); + return { backtrace, subscription }; +} +``` + + + + +`beginWithoutCrashHandler` forces the `enableCrashReporter` option (`TFSDKEnableCrashReporterKey` on iOS) to `false` even if you pass `true`. Plain `begin(...)` also starts a session without a crash handler, but the explicit call documents the contract in your code and is the recommended entry point. + +## Shared Attributes + +Both SDKs receive the same keys and values. The values are copies, not references: nothing joins the two products server-side, so a report and a session match only when the app wrote the same value to both. + +| Attribute | Value | Why | +| :--------------------------- | :-------------------------------------------------------- | :--------------------------------------------------------------------------------------------------- | +| `sauce.correlation_id` | Lowercase UUID v4, generated once per app launch | The join key. Search it in either console to find the matching report or session. | +| `sauce.sdk.coexistence_mode` | `backtrace_crash_owner` | Documents which product owns crashes in this build. Use it to filter mixed fleets. | +| `sauce.environment` | `beta`, `production`, or your own environment name | Separates beta traffic from other environments in both consoles. | +| `sauce.release` | `@` (for example `com.example.app@1.2.3`) | Ties reports and sessions to a release without relying on platform-specific version fields. | +| `sauce.dist` | Build number (`CFBundleVersion` or `versionCode`) | Distinguishes builds of the same version. | +| `mad.distribution_id` | Your Mobile App Distribution build id | Optional. Set it only when configured, so filters never see an empty string. | + +The Backtrace side additionally receives two attributes the app writes on every Sauce Mobile Beta session start: `sauce.mobile_beta.session_url` (the recording's address) and `sauce.mobile_beta.session_started` (the strings `"true"` or `"false"`). A launch can produce several sessions (after `stop()` and a resume), so overwrite these values in the listener every time. Never set them once. + +:::note Why one id per launch +A crash belongs to the launch it happened in. Generating `sauce.correlation_id` once per launch, before either SDK starts, guarantees that the report Backtrace writes at crash time and the session Sauce Mobile Beta recorded up to that moment carry the same value. For identity that spans launches, use `setUserId` with your real user id or a persisted identifier. Do not reuse the correlation id for that. +::: + +Limits on the Sauce Mobile Beta side: 64 attributes per session, keys up to 64 characters, values up to 1000 characters on iOS and 1024 on Android. On iOS and Android, `setAttribute` returns `false` when a value is rejected. The React Native binding returns nothing. Set every shared attribute before `beginWithoutCrashHandler`. The SDK keeps them and attaches them to every session it starts, including sessions started after `stop()`. + +:::caution +Do not use `setCorrelationId` or `identify` for the correlation id. Both are deprecated and write the user-identity field that `setUserId` writes (on Android only once per process), so they would collide with your real user id. Keep `setUserId` for the user and `setAttribute` for `sauce.correlation_id`. See [Identifying Your Users](/app-distribution/sdk/identifying-users) and [Session Attributes](/app-distribution/sdk/session-attributes). +::: + +## Finding a Crash's Session + +1. Index the attributes in Backtrace. Backtrace lets you filter and group on a custom attribute only after it has been indexed once per project under Project Settings > Attributes. Index `sauce.correlation_id` with the UUID format, and `sauce.mobile_beta.session_url` as a string. See [Indexing Attributes](/error-reporting/project-setup/attributes/). +2. From a crash report, copy the `sauce.correlation_id` value and search for it in the Sauce Labs Mobile App Distribution session list. The session recorded up to the crash carries the same value. +3. Or follow `sauce.mobile_beta.session_url` from the report directly to the recording. It points at the most recent session of that launch, which is the one running when the crash happened. + +In the other direction, filter Backtrace on `sauce.correlation_id` with the value shown on a session's attributes to see whether that launch crashed. + +## Crash APIs + +These methods exist in the API and do nothing: `installCrashHandler`, `enableCrashHandler`, and `disableCrashHandler`. `didLastSessionCrash` always returns `false`. In React Native, `isCrashReportingAvailable()` returns `false` and `getIntegrationInfo().coexistenceMode` is `backtrace_crash_owner`. Use Backtrace for crash history. + +## Production Builds + +Keep the Sauce Mobile Beta SDK out of store builds and ship Backtrace in every build. Backtrace is designed for production crash reporting. The Sauce Mobile Beta SDK records sessions for testers. On Android, declare the SDK with `debugImplementation` or in a beta flavor so release variants never package it. On iOS, gate the calls behind a build setting or a no-op wrapper. [Sauce Mobile Beta SDK in Production](/app-distribution/sdk/tf-production) describes the options, including the no-op wrapper pattern. The shared attributes go to Backtrace in production as well, which keeps `sauce.release` and `sauce.dist` consistent between beta and store reports. + +## Reference Apps + +This pattern is taken from the Sauce Labs demo apps and the React Native package's example app, all of which run the released SDKs: + +- [My Demo App for iOS repository](https://github.com/saucelabs/my-demo-app-ios), release [2.3.0](https://github.com/saucelabs/my-demo-app-ios/releases/tag/2.3.0). See `My Demo App/AppDelegate.swift` and `My Demo App/Utilities/TestFairyWrapper.swift`. +- [My Demo App for Android repository](https://github.com/saucelabs/my-demo-app-android), release [2.3.0](https://github.com/saucelabs/my-demo-app-android/releases/tag/2.3.0). See `MyApplication.java` and `app/src/mobileBeta/.../SauceMobileBetaIntegration.java`, which also asserts after `beginWithoutCrashHandler` that Backtrace's uncaught-exception handler is the one installed in the process. +- React Native example app: [testfairy/react-native-testfairy](https://github.com/testfairy/react-native-testfairy), release [3.0.0-rc](https://github.com/testfairy/react-native-testfairy/releases/tag/3.0.0-rc). See `example/src/observability.ts`. + +To try it, run a demo app with your own Backtrace submission URL and Sauce Mobile Beta token, then trigger a crash: More > Crash the App on iOS, or Crash app (debug) in the menu on Android. The process dies as a genuine crash. The Sauce Mobile Beta SDK does not intercept it. Relaunch the app: Backtrace uploads the pending report while Sauce Mobile Beta starts a new session. Open the report, copy its `sauce.correlation_id`, and search for it in the session list to land on the recording of the launch that crashed. diff --git a/docs/app-distribution/sdk/bug-tracking/jira-cloud.md b/docs/app-distribution/sdk/bug-tracking/jira-cloud.md new file mode 100644 index 0000000000..da2d897782 --- /dev/null +++ b/docs/app-distribution/sdk/bug-tracking/jira-cloud.md @@ -0,0 +1,253 @@ +--- +id: jira-cloud +title: Connecting Sauce Labs Mobile App Distribution to Jira Cloud +sidebar_label: Jira Cloud +description: Connect Mobile App Distribution to Jira Cloud so that tester feedback becomes a Jira issue with session data attached. +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +Bug tracking is an essential part of the software development process to identify, document, and resolve issues in a systematic manner. Sauce Labs Mobile App Distribution integrates with Jira Cloud so that tester feedback becomes a Jira issue that carries the session data developers need. + +Before using the bug-tracking features in Sauce Labs Mobile App Distribution, connect your Sauce Labs Mobile App Distribution account to Jira Cloud. The steps below establish a secure and authenticated connection between Sauce Labs Mobile App Distribution and Jira Cloud. + +## Creating a Jira API Token + +To connect Sauce Labs Mobile App Distribution to Jira Cloud, you'll need to create an API token in Jira. This token will be used to authenticate Sauce Labs Mobile App Distribution when accessing your Jira account. Follow these steps to create the API token: + +1. Log in to [https://id.atlassian.com/manage/api-tokens#](https://id.atlassian.com/manage/api-tokens#) and click on **Create API token**. + Create Jira API Token + +1. Give the new token a label that identifies the integration. + Set Sauce Labs Mobile App Distribution Jira Key + +1. Copy the API Token. + Copy the API Token + +## Configuring Jira in Your Sauce Labs Mobile App Distribution Settings + +To connect Sauce Labs Mobile App Distribution to Jira, you'll need to configure Jira settings in your Sauce Labs Mobile App Distribution account. Follow these steps to complete the configuration: + +1. Open your Sauce Labs Mobile App Distribution account Preferences. + Open Sauce Labs Mobile App Distribution preferences + +1. Choose **Integrations**, scroll to Jira and press **Add integration**. + Jira cloud + +1. Enter your Jira Username, API Token, and Jira URL in the next screen and press **Update Jira Settings**. + Configure Jira Cloud + +## (Optional) Installing the Sauce Labs Mobile App Distribution Chrome Extension (Deprecated) + +The Sauce Labs Mobile App Distribution Chrome Extension is available at [https://chrome.google.com/webstore/detail/testfairy-for-jira/joaafaemekbkgekhjbaldlllcnjifcee](https://chrome.google.com/webstore/detail/testfairy-for-jira/joaafaemekbkgekhjbaldlllcnjifcee). With this Chrome extension, every Jira issue that has a link to a Sauce Labs Mobile App Distribution session will contain the proper Sauce Labs Mobile App Distribution session, timeline, and logs embedded in the Jira issue. + +:::note +The Chrome extension is deprecated. Use the Jira add-on described in the next section instead. +::: + +## (Optional) Adding the Sauce Labs Mobile App Distribution Jira Add-on to Your Jira Account + +Add the Sauce Labs Mobile App Distribution Jira Add-on to your Jira account to include Sauce Labs Mobile App Distribution videos in Jira issues. Follow these steps to add the add-on: + +1. Open **Jira Settings**. + Open Jira Settings + +1. Open **Apps**. + Open Apps + +1. In the Apps menu, press **Find new apps**. + Find new apps + +1. Add **Sauce Labs Mobile App Distribution for Jira** to your account. + Sauce Labs Mobile App Distribution for Jira + +1. On the first issue created, click on the "3 dots" icon and choose **Sauce Labs Mobile App Distribution Session**. + Sauce Labs Mobile App Distribution session + + After the installation, the Jira issue looks like the following: + + Jira ticket + Jira popup + Application logs + +## (Optional) Mapping Jira Custom Fields + +

Highly Recommended

+ +Mapping Jira custom fields lets you automatically populate fields in a Jira issue when it is created, using standard Sauce Labs Mobile App Distribution session data or your own custom session attributes. This removes manual data entry and ensures every issue is created with consistent, complete information. + +Follow these steps to map Jira custom fields. + +### Prerequisites + +Before you begin, connect a Jira account by following the steps in [Creating a Jira API Token](#creating-a-jira-api-token) and [Configuring Jira in Your Sauce Labs Mobile App Distribution Settings](#configuring-jira-in-your-sauce-labs-mobile-app-distribution-settings) above. + +### Supported Field Types + +The following Jira field types can be mapped: + +| Field type | How it is mapped | +| --- | --- | +| Text field, Text area | Free text, a dynamic value, or a mix of both | +| Single select, Radio buttons | A fixed option from the field's list, or a dynamic value that resolves to one of the options | +| Multi-value fields with a fixed option list (for example, Labels) | A fixed option or a dynamic value | +| Priority | A fixed priority or a dynamic value | + +Other field types, such as user pickers, date pickers, number fields, and cascading selects, cannot be mapped and do not appear in the configuration table. + +:::caution Important +If your Jira project marks an unsupported field type as required, Jira may reject issues created by the integration. Make required fields in the target project either supported types or optional. +::: + +:::note Summary and Description +Mapping the standard **Summary** and **Description** fields requires an account-level feature that is disabled by default. Contact support to have it enabled. After it is enabled, both fields appear in the configuration table and accept dynamic values like any other field. Other standard Jira fields, such as Assignee and Reporter, cannot be mapped. +::: + +### Configuring the Integration + +1. From your list of apps, select the app you want to connect and click **Activate**. + Activate apps + + You can now configure the Jira fields: + Configure Jira fields + +1. In the Jira configuration screen, select the **Project Key** for the Jira project you want to connect to. + Connect project + +1. Select the **Issue Type** you want to configure. + Configure issue type + +:::tip +To verify the connection before saving, click **TEST**. If your mapping contains dynamic values, a dialog first asks you to enter a sample value for each one. A pop-up window then displays the result. A valid Jira link confirms that issue creation is working correctly. If you receive a **PENDING** response, review your connection configuration. +Connection test result +::: + +### Understanding the Field Table + +Each issue type has its own set of associated fields. In the configuration screen, each field is described by the following columns: + +| Column | Description | +| --- | --- | +| **Name** | The name of the Jira field. | +| **Type** | The field type as defined in Jira. | +| **Value** | The value to populate, drawn from Jira's available values, a predefined fixed value, or a dynamic value (see below). | +| **Required?** | Indicates whether the field is required or optional. | + +Required fields + +### Setting Field Values + +When defining values in the **Configure Fields** window, use one of the following methods. + +**Static values.** When you select a value from a dropdown list, that value is populated as-is (text) into the corresponding Jira field. +Values drop down + +**Predefined dynamic values.** Select the **Dynamic value** option to insert placeholders that are automatically replaced with live session data. Available placeholders include: + +| Placeholder | Description | +| --- | --- | +| `{user.id}` | The user ID of the running session. | +| `{session.timestamp}` | The timestamp of the session. | +| `{session.url}` | The session URL on the Sauce Labs Mobile App Distribution dashboard. The viewer must be signed in to open it. | +| `{session.publicUrl}` | A public link to the same session that opens without signing in. Anyone who has the link can view the session recording, and the link does not expire. | +| `{session.ipAddress}` | The IP address of the device running the session. | +| `{device.os}` | The operating system of the running device. | +| `{device.model}` | The device model of the handset. | +| `{device.osVersion}` | The OS version on the device (for example, an iPhone running iOS 12 returns 12). | +| `{app.name}` | The application name. | +| `{app.version}` | The build's `versionName` (Android) or `CFBundleShortVersionString` (iOS). Example: 1.7.0. | +| `{app.fullVersion}` | The `versionName` plus `versionCode` (Android) or `CFBundleVersion` (iOS). Example: 1.7.0 (1700). | +| `{feedback.text}` | The feedback message. | +| `{feedback.timestamp}` | The absolute timestamp of the feedback. | +| `{feedback.relTimestamp}` | The relative timestamp of the feedback (mm:ss) since the session started. | + +Dynamic value field + +:::caution Important +`{session.publicUrl}` places a link that requires no sign-in into every issue created from that mapping. Anyone who can read the Jira issue, including people outside your Sauce Labs account, can view the session recording, and the link cannot be revoked without a platform change. Accounts with strict data-sharing requirements should map `{session.url}` instead, which requires the viewer to be signed in. +::: + +You can combine placeholders with plain text in the same field, for example: + +```text +{feedback.text||No feedback text} | Session: {session.url} +``` + +To give an entire Jira project access to the recording, map a field such as **Session URL** to the public link: + +```text +{session.publicUrl||No session recorded} +``` + +**Placeholders are validated on save.** If a field contains a placeholder that does not exist, for example a misspelling such as `{sessio.publicUrl}`, the configuration is rejected and the message names both the placeholder and the Jira field. Nothing is saved until it is corrected. Jira text formatting such as `{color:red}` is still accepted, but single-word tags such as `{code}`, `{noformat}`, and a closing `{color}` cannot be used inside a mapped field. + +**Custom attribute values.** You can also map attributes defined in your app's code. Enter them in the **Dynamic value** field using the following structure: + +```text +{attr.[attribute_name]||[default_value]} +``` + +- **attribute_name**: The name of the attribute set in your code (for example, with the [`setAttribute`](/app-distribution/sdk/session-attributes) function). The value of this attribute is passed to Jira. +- **default_value**: A fallback value passed to Jira if the attribute is missing from the session. This ensures the Jira field always receives a valid entry. A default is mandatory for required fields. The configuration screen adds one automatically if you omit it. If a non-required field resolves to an empty value, the field is left out of the created issue. + +**Example:** `{attr.build_channel||production}` maps the value of your `build_channel` attribute to the Jira field. If no value is available for a given session, `production` is used instead. + +Attribute Setting + +### Setting Custom Attributes in Your App + +Set attributes from your app's code before or after the Sauce Mobile Beta SDK starts a session. See [Session Attributes](/app-distribution/sdk/session-attributes) for the full `setAttribute` reference. + + + + + +```java +TestFairy.setAttribute("build_channel", "production"); +``` + + + + + +```swift +TestFairy.setAttribute("build_channel", withValue: "production") +``` + + + + + +```objectivec +[TestFairy setAttribute:@"build_channel" withValue:@"production"]; +``` + + + + + +```javascript +TestFairy.setAttribute('build_channel', 'production'); +``` + + + + + +Keep the following rules in mind: + +- **Attribute names must contain only letters, digits, and underscores.** Names with spaces, hyphens, or dots can be stored, but cannot be referenced as a dynamic value in Jira mappings. +- Each session can store up to 64 attribute keys. Keys are limited to 64 characters and values to 1 KB. +- The call returns `true` (Android) or `YES` (iOS) on success, and `false`/`NO` if a limit is exceeded. Check the return value during development, as failures are otherwise silent. + +To verify that attributes were recorded, open the session in the dashboard and check the **Session Information** panel: recorded attributes appear under **Custom Attributes**. diff --git a/docs/app-distribution/sdk/crash-handler-testing.md b/docs/app-distribution/sdk/crash-handler-testing.md new file mode 100644 index 0000000000..030feab466 --- /dev/null +++ b/docs/app-distribution/sdk/crash-handler-testing.md @@ -0,0 +1,117 @@ +--- +id: crash-handler-testing +title: Testing Crash Reporting with Backtrace +sidebar_label: Testing Crash Reporting +description: Force a test crash on iOS to verify that Backtrace reports the crash and the Sauce Mobile Beta SDK records the session up to it. +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +

Beta release

+ +The Sauce Mobile Beta SDK does not install a crash handler. When you run it with Backtrace (Sauce Labs Error Reporting), Backtrace reports the crash and the SDK records the session up to the crash. The following steps show how to force a crash on iOS to verify that setup end to end. See [Using Sauce Mobile Beta with Backtrace](/app-distribution/sdk/backtrace-coexistence) for the setup itself. + +`TestFairy.crash()` is a test helper: it force-crashes the app with an invalid memory access. Backtrace captures the crash. + +:::note +`crash()` is an iOS-only helper. The Android SDK has no equivalent: throw a plain uncaught exception instead (for example, `throw new RuntimeException("test crash")` from a button handler), as the Sauce Labs My Demo App for Android does. +::: + +## What You'll Need + +- A working iOS app project with Backtrace initialized first and the Sauce Mobile Beta SDK started with `beginWithoutCrashHandler`. +- The shared attributes, including `sauce.correlation_id`, set on both SDKs before either starts. +- A build running on a device or Simulator without the Xcode debugger attached, so the crash reaches Backtrace's crash reporter. See `allowsAttachingDebugger` in [Configuring Backtrace for iOS](/error-reporting/platform-integrations/ios/configuration/). +- Basic knowledge of iOS development using either Objective-C or Swift. + +## Forcing a Crash + +Add a button that calls `crash()`: + + + + + +```objectivec +[TestFairy crash]; +``` + +Example + +```objectivec +#import "ViewController.h" +#import "TestFairy.h" + +@implementation ViewController +- (void)viewDidLoad { + [super viewDidLoad]; + + UIButton* button = [UIButton buttonWithType:UIButtonTypeRoundedRect]; + button.frame = CGRectMake(50, 50, 100, 30); + [button setTitle:@"Crash" forState:UIControlStateNormal]; + [button addTarget:self action:@selector(crashApp:) forControlEvents:UIControlEventTouchUpInside]; + [self.view addSubview:button]; +} + +- (IBAction)crashApp:(id)sender { + [TestFairy crash]; +} + +@end +``` + + + + + +```swift +TestFairy.crash() +``` + +Example + +```swift +import UIKit +import TestFairy + +class ViewController: UIViewController { + override func viewDidLoad() { + super.viewDidLoad() + + let button = UIButton(type: .roundedRect) + button.frame = CGRect(x: 50, y: 50, width: 100, height: 30) + button.setTitle("Crash", for: []) + button.addTarget(self, action: #selector(self.crashApp(_:)), for: .touchUpInside) + view.addSubview(button) + } + + @IBAction func crashApp(_ sender: AnyObject) { + TestFairy.crash() + } +} +``` + + + + + +## Expected Result + +1. The app terminates immediately. Backtrace's crash reporter writes the report to disk, and the Sauce Mobile Beta session ends at the crash. +2. Relaunch the app. Backtrace uploads the pending crash report while it initializes, and the Sauce Mobile Beta SDK starts a new session. +3. In Backtrace, open the new error. Its attributes carry the `sauce.correlation_id` value your app generated for the crashed launch, `sauce.sdk.coexistence_mode` set to `backtrace_crash_owner`, and, when your app mirrors it, `sauce.mobile_beta.session_url` pointing at the session recording. +4. In Sauce Labs Mobile App Distribution, search the session list for the same `sauce.correlation_id` value to open the recording that ends at the crash. + +The crash report appears in Backtrace only. The Sauce Labs Mobile App Distribution dashboard shows the session recording that ends at the crash. + +:::note +Backtrace lets you filter or group by `sauce.correlation_id` only after the attribute is indexed once per project under **Project Settings** > **Attributes**, with the UUID format. See [Indexing Attributes](/error-reporting/project-setup/attributes/). +::: diff --git a/docs/app-distribution/sdk/identifying-users.md b/docs/app-distribution/sdk/identifying-users.md new file mode 100644 index 0000000000..d6b89e8779 --- /dev/null +++ b/docs/app-distribution/sdk/identifying-users.md @@ -0,0 +1,100 @@ +--- +id: identifying-users +title: Identifying Your Users +sidebar_label: Identifying Your Users +description: Correlate Sauce Mobile Beta SDK session recordings with specific users and their traits with setUserId. +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +

Beta release

+ +With the user identification feature of the Sauce Mobile Beta SDK, you can enhance your testing and debugging process by efficiently correlating session recordings with specific users and their traits. This capability empowers you to gain deeper insights into how different users interact with your app and aids in the diagnosis of user-specific issues during testing. + +## Example Configuration + +Below are code examples illustrating how to use the SDK's `setUserId` method on various platforms: + + + + + +```java +TestFairy.setUserId(""); +``` + +Example + +```java +// Be sure to import the SDK +import com.testfairy.TestFairy; + +TestFairy.setUserId("john@example.com"); +``` + + + + + +```objectivec +[TestFairy setUserId:@""]; +``` + +Example + +```objectivec +// Be sure to import the SDK +#import "TestFairy.h" + +[TestFairy setUserId:@"john@example.com"]; +``` + +Swift + +```swift +import TestFairy + +TestFairy.setUserId("john@example.com") +``` + + + + + +```js +TestFairy.setUserId(""); +``` + +Example + +```js +// Be sure to import the SDK +import TestFairy from '@saucelabs/mobile-beta-react-native'; + +TestFairy.setUserId("john@example.com"); +``` + + + + + +Where `userId` is a string representing an association to your backend. We recommend passing values such as email, phone number, or user ID your app may use. This value may not be nil and is searchable via API and web search. + +## Important Notes + +To make the most effective use of user identification, keep the following in mind: + +- `setUserId` is the supported identity API. Call it with a non-null value chosen from user attributes such as email, phone number, or the user ID your app uses. +- You can call `setUserId` multiple times to update the identifier, before or after starting a session with `beginWithoutCrashHandler` (or `begin`). +- The user identifier you set with `setUserId` is searchable through the Sauce Labs Mobile App Distribution API and web search interface. +- `setCorrelationId` and `identify` are deprecated. They write the same user-identity field that `setUserId` writes (on Android, only the first call per session takes effect), so they overwrite, or are overwritten by, your real user ID. Replace `identify(id, traits)` with `setUserId(id)` plus one `setAttribute` call per trait. See [Session Attributes](/app-distribution/sdk/session-attributes). +- Do not use `setCorrelationId` or `identify` for the Backtrace (Sauce Labs Error Reporting) correlation id. The value that links a Backtrace crash report to a Sauce Mobile Beta session is the session attribute `sauce.correlation_id`, set with `setAttribute` before the session starts and sent to Backtrace as well. Keep `setUserId` for the real user. See [Using Sauce Mobile Beta with Backtrace](/app-distribution/sdk/backtrace-coexistence) and [Reserved Attributes for Backtrace Coexistence](/app-distribution/sdk/session-attributes#reserved-attributes-for-backtrace-coexistence). diff --git a/docs/app-distribution/sdk/ios/hiding-webview.md b/docs/app-distribution/sdk/ios/hiding-webview.md new file mode 100644 index 0000000000..aac59a4665 --- /dev/null +++ b/docs/app-distribution/sdk/ios/hiding-webview.md @@ -0,0 +1,43 @@ +--- +id: hiding-webview +title: Hiding Webview Elements from Video +sidebar_label: Hiding Webview Elements +description: Hide HTML elements in UIWebView and WKWebView components from session recordings made by the Sauce Mobile Beta SDK on iOS. +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +

Beta release

+ +Sauce Labs Mobile App Distribution provides developers with the capability to conceal specific HTML elements in recorded videos displayed in `UIWebView` or `WKWebView` components in their application's user interface. This feature allows developers to enhance security and privacy by excluding sensitive information from video recordings. + +In certain scenarios, developers might need to prevent some information, such as credit card details or other sensitive content, from being displayed in recorded video sessions. Sauce Labs Mobile App Distribution's **Hide Webview Elements from Video** feature addresses this concern by allowing developers to selectively hide designated HTML elements from the video playback. + +## Syntax + +The process of hiding a specific element from the recorded video involves utilizing the static instance method `hideWebViewElements` in the `TestFairy` class. This method requires a valid CSS selector as its parameter to target the desired HTML element for concealment: + +```css +[TestFairy hideWebViewElements:@"body"]; +[TestFairy hideWebViewElements:@"div#username,div#password"]; +[TestFairy hideWebViewElements:@".col-12,h1 .header"]; +``` + +Sauce Labs Mobile App Distribution finds any `UIWebView` or `WKWebView` in the view hierarchy and hides a given HTML element based on a valid CSS selector. + +## Example + +Suppose you have a demo video showcasing your application's user interface. On the left side of the screen is the regular app interface, while the right side displays a screenshot with the HTML elements hidden: + +iphone no hidden HTML elements +iphone hidden HTML elements + +This functionality provides a clear visual representation of how Sauce Labs Mobile App Distribution's feature conceals elements in the video. + +:::note +- Sauce Labs Mobile App Distribution ensures that elements are hidden from screenshots before they are uploaded, thereby preserving the security and privacy of sensitive information. +- The `hideWebViewElements` method supports the inclusion of multiple comma-separated CSS selectors. This capability allows you to hide multiple HTML elements simultaneously. +- You can extend the list of hidden elements at any time by making additional calls to the `hideWebViewElements` method. +::: diff --git a/docs/app-distribution/sdk/ios/integrating-ios.md b/docs/app-distribution/sdk/ios/integrating-ios.md new file mode 100644 index 0000000000..f8e80b74a3 --- /dev/null +++ b/docs/app-distribution/sdk/ios/integrating-ios.md @@ -0,0 +1,193 @@ +--- +id: integrating-ios +title: Integrating the iOS SDK +sidebar_label: Integrating iOS SDK +description: Install and start the Sauce Mobile Beta SDK in a Swift or Objective-C iOS app. +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +

Beta release

+ +:::info Beta release +The Sauce Mobile Beta SDK is in beta. The current release candidates are 2.2.0-rc for iOS and Android and 3.0.0-rc for React Native. + +- Final: the artifact names and the API. +- Can still change before general availability: the version numbers, the iOS package URL, and this documentation. + +Share feedback with your Sauce Labs representative. +::: + +Integrating the Sauce Mobile Beta SDK into your app helps you better understand how your app performs on real devices. It tells you when and how people are using your app, and provides you with any metrics you may need to optimize your user experience and code. +You get to: + +* Track app use. +* Record screen video and other metrics. +* Understand user flow, using checkpoints. +* Grab NSLogs from client and report to server. +* Collect in-app feedback from your testers. + +:::note +The SDK does not install a crash handler. Crash reporting is provided by Backtrace (Sauce Labs Error Reporting), and the two SDKs are designed to run in the same app. Initialize Backtrace first, then start the SDK with `beginWithoutCrashHandler`. See [Using Sauce Mobile Beta with Backtrace](/app-distribution/sdk/backtrace-coexistence). +::: + +## Adding the SDK + +The SDK for iOS is distributed with Swift Package Manager. The current release candidate is `2.2.0-rc` and requires iOS 11 or later. + +### Swift Package Manager + +The CocoaPods pod `SauceMobileBeta` is not published yet. Use Swift Package Manager to add the SDK. + +:::note +Screenshots were taken with Xcode 13.1. The steps are the same in current Xcode versions. +::: + +| 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.2.0-rc` | + +1. Select your project from the Xcode navigator to open your project's configuration. +2. Make sure your project is selected from the Project and Target list. +3. Click the **Package Dependencies** toolbar item. +4. Click the '+' icon to add a package. + + +1. In the newly opened dialog, enter the Sauce Mobile Beta package URL `https://github.com/testfairy/testfairy-ios-sdk-swift-package` in the top right search bar. +2. Set the **Dependency Rule** to **Exact Version** and enter `2.2.0-rc`. +3. Click **Add Package**. + + +1. After the package has been downloaded, in the newly opened dialog, make sure the `SauceMobileBeta` product is selected in the "Package Product" column. +2. Make sure the right target is selected in the "Add to target" column. +3. Click **Add Package**. + + +:::note Why Exact Version? +`2.2.0-rc` is a semantic-version pre-release. The default "Up to Next Major Version" rule (`from: "2.2.0"`) skips pre-releases, so the package would not resolve. Pin the exact version until the general availability release. The product name is `SauceMobileBeta` and the module name is `TestFairy`. The package URL may move to a Sauce Labs-owned repository before general availability. +::: + +If you declare dependencies in a `Package.swift` manifest instead: + +```swift +dependencies: [ + .package(url: "https://github.com/testfairy/testfairy-ios-sdk-swift-package.git", exact: "2.2.0-rc") +], +targets: [ + .target(name: "MyApp", dependencies: [ + .product(name: "SauceMobileBeta", package: "testfairy-ios-sdk-swift-package") + ]) +] +``` + +## Initializing the SDK + +Start the SDK with `beginWithoutCrashHandler`. It starts a session without installing a crash handler and forces the `TFSDKEnableCrashReporterKey` begin option to `NO`, even if you pass `YES`. `begin` also starts a session without a crash handler. Use `beginWithoutCrashHandler` to make the intent explicit. When Backtrace is part of the app, initialize Backtrace before this call. See [Using Sauce Mobile Beta with Backtrace](/app-distribution/sdk/backtrace-coexistence) for the full initialization order and the shared `sauce.correlation_id` attribute. + +Replace `` with your app token. Once logged in, your app token is available from your [account preferences](https://app.testfairy.com/settings#apptoken). + + + + + +1. Open your AppDelegate.m file. + +2. Add this code to your app: + +```objectivec +#import "TestFairy.h" + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + + // Initialize Backtrace first when it is part of the app. + + [TestFairy beginWithoutCrashHandler:@""]; + + // the rest of the didFinishLaunchingWithOptions method + // ... + return YES; +} +``` + + + + + +1. Import the SDK + + With Swift Package Manager, import the module directly in every Swift file that uses the SDK. The package product is `SauceMobileBeta` and the module name is `TestFairy`: + +```swift +import TestFairy +``` + + An Objective-C bridging header also works, for example in a project that already has one. This process only needs to be done once per project. If you have already done so, update your bridging header file. + * Right-click on your project and select **New File**. + * Select Header File.h + * Save as Bridging.h in your project + * Click on Bridging.h to open it in editor + * Add the following line to the code: + +```objectivec +#import "TestFairy.h" +``` + +:::note +If the header is not found, try the framework-style import: +```objectivec +#import "TestFairy/TestFairy.h" +``` +::: + + Update Build Settings with the new bridging header: + * Click on your project + * Select Build Settings tab + * Select the "All" filter to find Swift Compiler - General: Objective-C Bridging Header + * Edit Swift Compiler - General: Objective-C Bridging Header (double-click to edit). + * Drag "Bridging.h" from the source tree onto the edit box opened + +2. Open your AppDelegate.swift file. + +3. Add this code to your app: + +```swift +import UIKit +import TestFairy + +func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + // Initialize Backtrace first when it is part of the app. + + TestFairy.beginWithoutCrashHandler("") + + // the rest of the didFinishLaunchingWithOptions method + // ... + return true +} +``` + + + + +## Using PencilKit for Better Feedback +You can give your users a better set of tools to markup any screenshots provided during feedback by adding PencilKit to your project. Add the PencilKit.framework to your project. + +:::note +This requires iOS 13 and Xcode 11. +::: + +
Pencilkit + +If a screenshot is attached to the feedback, your users can edit the screenshot by tapping on it and using PencilKit to mark it up. + +
Pencilkit Feedback diff --git a/docs/app-distribution/sdk/ios/log-network.md b/docs/app-distribution/sdk/ios/log-network.md new file mode 100644 index 0000000000..e37df8c112 --- /dev/null +++ b/docs/app-distribution/sdk/ios/log-network.md @@ -0,0 +1,30 @@ +--- +id: log-network +title: Log Network +sidebar_label: Log Network +description: Log your iOS app's network requests and NSLog output to Mobile App Distribution sessions with the Sauce Mobile Beta SDK. +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +

Beta release

+ +Sauce Labs Mobile App Distribution provides a powerful feature that allows you to log all network requests made by your mobile app. This logging capability facilitates the monitoring of network access, enabling you to identify potential issues, such as **slow requests** or HTTP **4xx** error codes. + +These issues might be challenging to locate manually but can significantly impact user experience. Sauce Labs Mobile App Distribution will list all network requests on the session page. Fixing these issues will significantly improve the experience for your users. + +example issues + +:::note +See our [Code Examples](/app-distribution/sdk/logging) for more information. +::: + +## Sending NSLog to Sauce Labs Mobile App Distribution + +The Sauce Mobile Beta SDK records your app while used so you can watch recorded sessions to solve problems faster. The SDK can record videos, screenshots, custom events, logs, and device metrics. + +:::note +The [Remote Logging method](/app-distribution/sdk/remote-logging) explains how to set iOS apps to send NSLogs to Sauce Labs Mobile App Distribution (from iOS 10 and above). +::: \ No newline at end of file diff --git a/docs/app-distribution/sdk/logging.md b/docs/app-distribution/sdk/logging.md new file mode 100644 index 0000000000..fa1eba0e54 --- /dev/null +++ b/docs/app-distribution/sdk/logging.md @@ -0,0 +1,261 @@ +--- +id: logging +title: Logging +sidebar_label: Logging +description: Log your app's network requests and handled exceptions to the session timeline with the Sauce Mobile Beta SDK. +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +

Beta release

+ +The Sauce Mobile Beta SDK can log your app's network requests and handled exceptions to the session timeline, giving you an effortless way to monitor your app's network access and non-fatal errors. For the iOS-specific network setup, see [Log Network](/app-distribution/sdk/ios/log-network). + +example issues + + + + + +```js +TestFairy.addNetworkEvent(URI uri, String method, int code, long startTimeMillis, long endTimeMillis, long requestSize, long responseSize, String errorMessage); +``` + +Example + +If you are using `OkHttp` or `Retrofit` all you need to do is add `CustomHttpInterceptor` to your client: + +```js +// Be sure to import the SDK +import com.testfairy.TestFairy; + +public class CustomHttpInterceptor implements Interceptor { + @Override + public Response intercept(@NonNull Chain chain) throws IOException { + + Request request = chain.request(); + long startTimeMillis = System.currentTimeMillis(); + Long requestSize = request.body() != null ? request.body().contentLength() : 0; + Response response; + try { + response = chain.proceed(request); + } catch (IOException e) { + long endTimeMillis = System.currentTimeMillis(); + TestFairy.addNetworkEvent(request.url().uri(), request.method(), -1, startTimeMillis, endTimeMillis, requestSize, -1, e.getMessage()); + throw e; + } + + long endTimeMillis = System.currentTimeMillis(); + long responseSize = response.body() != null ? response.body().contentLength() : 0; + TestFairy.addNetworkEvent(request.url().uri(), request.method(), response.code(), startTimeMillis, endTimeMillis, requestSize, responseSize, null); + return response; + } +} + +OkHttpClient client = new OkHttpClient.Builder() + .addInterceptor(new CustomHttpInterceptor()) + .build(); +``` + + + + + +```js +[TestFairy addNetwork:(NSURLSessionTask *)task error:(NSError *)error] +``` + +Example + +If you are using `NSURLConnection`, all you need to do is add `callback` to your request at the beginning and end of the request. + +:::note + +If you have `AFNetworking` added to your project, network requests are automatically captured when enabled in your build settings. + +::: + +```js +// Be sure to import the SDK +#import "TestFairy.h" + +__block NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { + // ... + [TestFairy addNetwork:task error:error]; +}]; +[TestFairy addNetwork:task error:nil]; +[task resume]; +``` + + + + + +```js +TestFairy.addNetwork(, error:) +``` + +Example + +If you are using `URLConnection`, all you need to do is add callback to your request at the beginning and end of the request: + +```js +var task: URLSessionTask? = nil +task = URLSession.shared.dataTask(with: URL(string:"")!) { (data, response, error) in + TestFairy.addNetwork(task, error: error) +} +TestFairy.addNetwork(task, error: nil) +task?.resume() +``` + +With `Alamofire`: + +```js +import Alamofire + +NotificationCenter.default.addObserver(forName: Request.didResume, object: nil, queue: nil) { (notification) in + let info = notification.userInfo + let request = info?["org.alamofire.notification.key.request"] as! Request + request.tasks.forEach { TestFairy.addNetwork($0, error: nil) } +} + +NotificationCenter.default.addObserver(forName: Request.didComplete, object: nil, queue: nil) { (notification) in + let info = notification.userInfo + let request = info?["org.alamofire.notification.key.request"] as! Request + request.tasks.forEach { TestFairy.addNetwork($0, error: nil) } +} +``` + + + + + +Example + +Sauce Labs Mobile App Distribution supports network logging for `Fetch API`. Call the following method to start capturing network calls. + +```js +// Capture network logs +TestFairy.enableNetworkLogging(window); + +// Capture network logs, including http headers and content +TestFairy.enableNetworkLogging(window, { includeHeaders: true, includeBodies: true }); + +// Disable network logging +TestFairy.disableNetworkLogging(window); +``` + + + + +## Exception Logging + +Sauce Labs Mobile App Distribution allows developers to log up to five exceptions or errors for a session. + +:::note + +It does not mark the session as crashed. It only logs the exception or error to the session. + +::: + + + + + +```js +TestFairy.logThrowable(); +``` + +Example + +```js +// Be sure to import the SDK +import com.testfairy.TestFairy; + +TestFairy.logThrowable(new Throwable("Some Message")); +``` + + + + + +```js +[TestFairy logError:]; +[TestFairy logError: stacktrace:>]; +``` + +Example + +```js +// Be sure to import the SDK +#import "TestFairy.h" + +[TestFairy logError:[NSError errorWithDomain:@"com.your.domain" code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Some Message"}]]; +``` + + + + + +```js +TestFairy.logError() +TestFairy.logError(, stacktrace:<[String]>) +``` + +Example + +```js +let error = NSError(domain: "com.your.domain", code: -1, userInfo: [NSLocalizedDescriptionKey : "Some Message"]) +TestFairy.logError(error) +``` + + + + + +```js +TestFairy.logException(); +``` + +Example + +```js +// Be sure to import the SDK +import TestFairy from '@saucelabs/mobile-beta-react-native'; + +var error = new Error("Some Message"); +TestFairy.logException(error); +``` + +We recommend replacing the `Global Handler` with a custom method, automatically sending the exception to Sauce Labs Mobile App Distribution sessions. One suggestion we have is to add a method that looks like this: + +```js +const ErrorUtils = global.ErrorUtils; +var originalGlobalHandler = ErrorUtils.getGlobalHandler(); +ErrorUtils.setGlobalHandler((error, isFatal) => { + TestFairy.logException(error); + originalGlobalHandler.handleException(error, isFatal); +}); +``` + + + + diff --git a/docs/app-distribution/sdk/map-locations.md b/docs/app-distribution/sdk/map-locations.md new file mode 100644 index 0000000000..63d5e50acf --- /dev/null +++ b/docs/app-distribution/sdk/map-locations.md @@ -0,0 +1,120 @@ +--- +id: map-locations +title: Using Map Locations +sidebar_label: Using Map Locations +description: Send location updates from your app with the Sauce Mobile Beta SDK and show them on a map on the session page. +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +

Beta release

+ +The Sauce Mobile Beta SDK does not require location permissions and does not track location out of the box. + +To send location information to Sauce Labs Mobile App Distribution, add location permissions to your app and use the code below to call `TestFairy.updateLocation`. The location will then be presented on a map as part of the session page. + + + + + +To record locations, you must first add the correct permissions to your application. Depending on your application's needs, you may want to add one or both of the following permissions to your `AndroidManifest.xml`. + +```xml + + +``` + +```js +TestFairy.updateLocation(android.location.Location location) +``` + +Example + +```java +import android.content.Context; +import android.location.Criteria; +import android.location.Location; +import android.location.LocationListener; +import android.location.LocationManager; +import androidx.appcompat.app.AppCompatActivity; +import android.os.Bundle; + +import com.testfairy.TestFairy; + +public class MainActivity extends AppCompatActivity implements LocationListener { + + private LocationManager locationManager; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + // Get the location manager + locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); + + Criteria criteria = new Criteria(); + String provider = locationManager.getBestProvider(criteria, false); + Location location = locationManager.getLastKnownLocation(provider); + + // Initialize the location fields + if (location != null) { + onLocationChanged(location); + } + } + + @Override + public void onLocationChanged(Location location) { + TestFairy.updateLocation(location); + } +} +``` + + + + + +To record locations, call the static instance method `updateLocation` in the `TestFairy` class, passing in a collection of `CLLocations`. + +```js +NSArray *locations = ... +[TestFairy updateLocation:locations]; +``` + +Example + +```js +#import "TestFairy.h" + +@interface NewRunViewController: UIViewController +- (void)viewDidLoad +{ + [super viewDidLoad]; + + self.locationManager = [[CLLocationManager alloc] init]; + self.locationManager.delegate = self; + self.locationManager.distanceFilter = kCLDistanceFilterNone; + self.locationManager.desiredAccuracy = kCLLocationAccuracyBest; + [self.locationManager startUpdatingLocation]; +} + +... + +-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations { + [TestFairy updateLocation:locations]; +} + +@end +``` + + + + diff --git a/docs/app-distribution/sdk/migration/android.md b/docs/app-distribution/sdk/migration/android.md new file mode 100644 index 0000000000..4b34b337f2 --- /dev/null +++ b/docs/app-distribution/sdk/migration/android.md @@ -0,0 +1,282 @@ +--- +id: android +title: Migrating an Android App from the TestFairy SDK +sidebar_label: Android +description: Move an Android app from the TestFairy SDK 1.x to the Sauce Mobile Beta SDK. +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +

Beta release

+ +:::info Beta release +The Sauce Mobile Beta SDK is in beta. The current release candidates are 2.2.0-rc for iOS and Android and 3.0.0-rc for React Native. + +- Final: the artifact names and the API. +- Can still change before general availability: the version numbers, the iOS package URL, and this documentation. + +Share feedback with your Sauce Labs representative. +::: + +Follow these steps to move an Android app from the TestFairy SDK 1.x to the Sauce Mobile Beta SDK 2.2.0-rc. Read [Migrating from the TestFairy SDK](/app-distribution/sdk/migration/overview) first for what changes and why. For the full install reference of the new SDK, see [Integrating the Android SDK](/app-distribution/sdk/android/integrating-android). + +The Maven coordinate changes. The Java package `com.testfairy` and the `TestFairy` API do not, so `import com.testfairy.TestFairy;` and your `TestFairy.*` calls keep compiling. Both Java and Kotlin apps are supported. + +:::caution Order matters +Remove `com.testfairy:testfairy-android-sdk` and `com.testfairy:testfairy-android-ndk` from every module before you add the Sauce Mobile Beta artifact. Both families contain the same `com.testfairy` classes, so a build that contains both fails with `Duplicate class` 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 + +Remove every TestFairy SDK 1.x dependency from every module's `build.gradle` (or `build.gradle.kts`), including the NDK artifact. Check library modules too: a transitive copy pulled in by another module also fails the build. + +```groovy +// Remove these lines from every module. +implementation 'com.testfairy:testfairy-android-sdk:1.+@aar' +implementation 'com.testfairy:testfairy-android-ndk:1.+@aar' +``` + +If the project declared `https://maven.testfairy.com` only for the TestFairy SDK 1.x, keep the repository. The Sauce Mobile Beta artifact is served from it as well. If the declaration has a `content { includeGroup 'com.testfairy' }` filter, change the group to `com.saucelabs.mobilebeta` as shown in step 2, or Gradle will not look up the new artifact there. + +**Verify.** The following command prints nothing from the `com.testfairy` group: + +```bash +./gradlew :app:dependencies --configuration debugRuntimeClasspath | grep testfairy +``` + +Repeat it for every module that declared the dependency. + +## 2. Add the Sauce Mobile Beta Artifact + +The Sauce Mobile Beta SDK for Android is `com.saucelabs.mobilebeta:sauce-mobile-beta-android`, served from `https://maven.testfairy.com`. It is not on Maven Central or jcenter. It supports API level 16 or later on its own, and API level 21 or later with Backtrace (see step 4). + +Make sure the repository is declared in `settings.gradle` (or in the root `build.gradle` for older project layouts). The `content` filter is optional and limits the repository to the `com.saucelabs.mobilebeta` group: + + + + + +```groovy +// settings.gradle +dependencyResolutionManagement { + repositories { + google() + mavenCentral() + maven { + url 'https://maven.testfairy.com' + content { includeGroup 'com.saucelabs.mobilebeta' } + } + } +} +``` + + + + + +```kotlin +// settings.gradle.kts +dependencyResolutionManagement { + repositories { + google() + mavenCentral() + maven { + url = uri("https://maven.testfairy.com") + content { includeGroup("com.saucelabs.mobilebeta") } + } + } +} +``` + + + + + +Then add the dependency to your app module. Pin the exact version. Do not use version wildcards such as `2.+`: they resolve unpredictably for pre-release versions. + + + + + +```groovy +dependencies { + implementation "com.saucelabs.mobilebeta:sauce-mobile-beta-android:2.2.0-rc" +} +``` + + + + + +```kotlin +dependencies { + implementation("com.saucelabs.mobilebeta:sauce-mobile-beta-android:2.2.0-rc") +} +``` + + + + + +Prefer `debugImplementation` or a beta flavor configuration such as `betaImplementation`, so that store builds do not ship the SDK while Backtrace stays under `implementation`. See [Sauce Mobile Beta SDK in Production](/app-distribution/sdk/tf-production). + +Your imports stay the same: + +```java +import com.testfairy.TestFairy; +``` + +**Verify.** Gradle sync succeeds and the dependency graph lists `com.saucelabs.mobilebeta:sauce-mobile-beta-android:2.2.0-rc`: + +```bash +./gradlew :app:dependencies --configuration debugRuntimeClasspath | grep mobilebeta +``` + +## 3. Update the Initialization + +Replace `begin` with `beginWithoutCrashHandler` in the `onCreate` method of your `Application` subclass. It starts a session, never installs a crash handler, and forces the `enableCrashReporter` option to `false` even if you pass `true` through the options overload `beginWithoutCrashHandler(context, appToken, options)`. Replace `` with the app token from your Sauce Labs Mobile App Distribution dashboard. + + + + + +```java +import android.app.Application; +import com.testfairy.TestFairy; + +public class MyApplication extends Application { + + @Override + public void onCreate() { + super.onCreate(); + // Before: + // TestFairy.begin(this, ""); + + // After. Initialize Backtrace before this line when it is part of the app. + TestFairy.beginWithoutCrashHandler(this, ""); + } +} +``` + + + + + +```kotlin +import android.app.Application +import com.testfairy.TestFairy + +class MyApplication : Application() { + + override fun onCreate() { + super.onCreate() + // Before: + // TestFairy.begin(this, "") + + // After. Initialize Backtrace before this line when it is part of the app. + TestFairy.beginWithoutCrashHandler(this, "") + } +} +``` + + + + + +The `Application` class stays registered in `AndroidManifest.xml` as before. If you pass an options map, remove the `enableCrashReporter` key. The SDK forces it to `false` 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: + +```java +// Delete these calls. They are no-ops in the Sauce Mobile Beta SDK. +TestFairy.enableCrashHandler(); +TestFairy.disableCrashHandler(); +TestFairy.installCrashHandler(context, ""); // does not start a session +TestFairy.didLastSessionCrash(context); // always returns false +``` + +If your app used `installCrashHandler` on its own, as a crash handler without a session, that use case is now covered by Backtrace alone. Any code built on `didLastSessionCrash`, for example a "the app crashed last time" prompt, needs a Backtrace-based replacement or removal. + +Crash reporting is provided by Backtrace. Add the Backtrace Android SDK by following [Setting Up Backtrace for Android](/error-reporting/platform-integrations/android/setup/), and initialize it before `beginWithoutCrashHandler` so that it is the only crash owner. For NDK crashes, also follow [Native Crash Integration](/error-reporting/platform-integrations/android/native-crash-integration/), which takes over the role of the `testfairy-android-ndk` artifact you removed. New to Backtrace? Start with [Getting Started](/error-reporting/getting-started/). + +Two requirements come with Backtrace: + +- Use Backtrace Android SDK 3.7.14 or later. That release added `BacktraceClient.addAttribute`, which the correlation step uses to copy the session URL into Backtrace. On 3.7.13 or earlier, use `((BacktraceDatabase) client.database).addAttribute(key, value)` instead. See [Configuring Backtrace for Android](/error-reporting/platform-integrations/android/configuration/). +- Backtrace Android requires API level 21. The Sauce Mobile Beta SDK alone supports API level 16, so an app that ships both must set `minSdk 21`. + +**Verify.** The following command prints nothing: + +```bash +grep -rn "installCrashHandler\|enableCrashHandler\|disableCrashHandler\|didLastSessionCrash" --include="*.java" --include="*.kt" app/src +``` + +## 5. Review ProGuard, R8, and Symbol Uploads + +The Sauce Mobile Beta AAR bundles its consumer ProGuard rules, so Gradle applies them automatically when minification is enabled. You do not need to add rules for the SDK. If your `proguard-rules.pro` still contains the rules from the TestFairy SDK 1.x, keeping them is harmless: + +```text +-keep class com.testfairy.** { *; } +-dontwarn com.testfairy.** +-keepattributes Exceptions, Signature, LineNumberTable +-dontusemixedcaseclassnames +``` + +An app on the Sauce Mobile Beta SDK sends no crash reports to Sauce Labs Mobile App Distribution, so crash symbolication for it happens in Backtrace. Upload your ProGuard mapping files and native symbols to Backtrace: see [ProGuard Deobfuscation](/error-reporting/platform-integrations/android/proguard-deobfuscation/) for mapping files and [Upload Native Symbols](/error-reporting/platform-integrations/android/native-crash-integration/#upload-native-symbols) for NDK symbols. + +**Verify.** A minified build runs, and a crash report in Backtrace shows your class and method names. + +## 6. Add the Correlation Attributes + +To match a Backtrace crash report with the session recorded up to the crash, both SDKs must carry the same `sauce.correlation_id` attribute: one lowercase UUID v4 generated per app launch before either SDK starts, together with the other reserved attributes. Use `TestFairy.addSessionStateListener` before `beginWithoutCrashHandler` to copy the session URL into Backtrace on every session start. + +The contract is defined once, in [Using Sauce Mobile Beta with Backtrace](/app-distribution/sdk/backtrace-coexistence), which has the initialization order and a complete `Application` example in Kotlin, and [Reserved Attributes for Backtrace Coexistence](/app-distribution/sdk/session-attributes#reserved-attributes-for-backtrace-coexistence). + +Do not use the deprecated `setCorrelationId` or `identify` methods for the correlation id. They write the user-identity field that `setUserId` writes, and on Android only the first call per session takes effect. See [Identifying Your Users](/app-distribution/sdk/identifying-users). + +**Verify.** A session's attributes in Sauce Labs Mobile App Distribution show `sauce.correlation_id`, and a Backtrace report from the same launch shows the same value. + +## 7. Validate Before Release + +1. Inspect the runtime dependency graph of every module. `./gradlew :app:dependencies --configuration debugRuntimeClasspath | grep testfairy` must list nothing from the `com.testfairy` group. +2. A clean build succeeds. A `Duplicate class` error for `com.testfairy` classes means a TestFairy SDK 1.x artifact is still on the classpath, possibly as a transitive dependency of another module. Go back to step 1. +3. Run a debug build with both SDKs initialized and force one crash (and one native crash if you enabled the native integration). The process dies as a genuine crash. Relaunch the app: the crashes appear in Backtrace only, and the Sauce Mobile Beta SDK starts a new session. +4. Submit feedback from the app and confirm that the session shows `sauce.correlation_id` in its attributes, with the same value as the Backtrace report from that launch. +5. Confirm that release variants do not package the Sauce Mobile Beta SDK while Backtrace stays. See [Sauce Mobile Beta SDK in Production](/app-distribution/sdk/tf-production). + +## Troubleshooting + +**`Could not find com.saucelabs.mobilebeta:sauce-mobile-beta-android:2.2.0-rc`** or a `Could not GET` error for a `jcenter.bintray.com` URL. The SDK is served from `https://maven.testfairy.com`, not from jcenter or Maven Central. Add the repository as shown in step 2. + +**`Duplicate class` errors for `com.testfairy` classes.** A TestFairy SDK 1.x artifact (`com.testfairy:testfairy-android-sdk` or `com.testfairy:testfairy-android-ndk`) is still on the classpath, possibly through another module. Run the dependency graph command from step 1 for every module and remove it. + +**`minSdk` conflict after adding Backtrace.** Backtrace Android requires API level 21. Set `minSdk 21` for the combined app. + +**`addAttribute` is not found on `BacktraceClient`.** You are on Backtrace Android SDK 3.7.13 or earlier. Upgrade to 3.7.14 or later, or use `((BacktraceDatabase) client.database).addAttribute(key, value)`. + +**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 Android](/error-reporting/platform-integrations/android/setup/). + +**`didLastSessionCrash` returns `false` after a crash.** The SDK returns `false` by design. Query Backtrace for crash history. diff --git a/docs/app-distribution/sdk/migration/ios.md b/docs/app-distribution/sdk/migration/ios.md new file mode 100644 index 0000000000..0dd4b6cf0f --- /dev/null +++ b/docs/app-distribution/sdk/migration/ios.md @@ -0,0 +1,246 @@ +--- +id: ios +title: Migrating an iOS App from the TestFairy SDK +sidebar_label: iOS +description: Move an iOS app from the TestFairy SDK 1.x to the Sauce Mobile Beta SDK. +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +

Beta release

+ +:::info Beta release +The Sauce Mobile Beta SDK is in beta. The current release candidates are 2.2.0-rc for iOS and Android and 3.0.0-rc for React Native. + +- Final: the artifact names and the API. +- Can still change before general availability: the version numbers, the iOS package URL, and this documentation. + +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.2.0-rc. Read [Migrating from the TestFairy SDK](/app-distribution/sdk/migration/overview) first for what changes and why. For the full install reference of the new SDK, see [Integrating the iOS SDK](/app-distribution/sdk/ios/integrating-ios). + +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. + +:::caution Order matters +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: + +```ruby +# Podfile: remove this line. +pod 'TestFairy' +``` + +```bash +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. + +```text +# 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.2.0-rc 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: + +```bash +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 11 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.2.0-rc` | + +In Xcode, select the project, open **Package Dependencies**, click **+**, enter the package URL, set the **Dependency Rule** to **Exact Version** `2.2.0-rc`, 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.2.0-rc` 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: + +```swift +dependencies: [ + .package(url: "https://github.com/testfairy/testfairy-ios-sdk-swift-package.git", exact: "2.2.0-rc") +], +targets: [ + .target(name: "MyApp", dependencies: [ + .product(name: "SauceMobileBeta", package: "testfairy-ios-sdk-swift-package") + ]) +] +``` + +:::note Why Exact Version? +`2.2.0-rc` is a semantic-version pre-release. The default **Up to Next Major Version** rule (`from: "2.2.0"`) skips pre-releases, so the package would not resolve. Pin the exact version until the general availability release. +::: + +**Verify.** `Package.resolved` lists `testfairy-ios-sdk-swift-package` at version `2.2.0-rc`, 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 `` with your app token from your [account preferences](https://app.testfairy.com/settings#apptoken). + + + + + +With Swift Package Manager, import the module directly. The package product is `SauceMobileBeta`, but the module is `TestFairy`. An existing bridging header with `#import "TestFairy.h"` also keeps working. + +```swift +import UIKit +import TestFairy + +func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + // Before: + // TestFairy.begin("") + + // After. Initialize Backtrace before this line when it is part of the app. + TestFairy.beginWithoutCrashHandler("") + + // the rest of the didFinishLaunchingWithOptions method + return true +} +``` + + + + + +```objectivec +#import "TestFairy.h" + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + // Before: + // [TestFairy begin:@""]; + + // After. Initialize Backtrace before this line when it is part of the app. + [TestFairy beginWithoutCrashHandler:@""]; + + // 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 +// Delete these calls. They are no-ops in the Sauce Mobile Beta SDK. +TestFairy.installCrashHandler("") // does not start a session +TestFairy.enableCrashHandler() +TestFairy.disableCrashHandler() +TestFairy.didLastSessionCrash() // always returns false +``` + + + + + +```objectivec +// Delete these calls. They are no-ops in the Sauce Mobile Beta SDK. +[TestFairy installCrashHandler:@""]; // 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](/error-reporting/platform-integrations/ios/setup/), and initialize it before `beginWithoutCrashHandler` so that it is the only crash owner. New to Backtrace? Start with [Getting Started](/error-reporting/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: + +```bash +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. + +1. Keep **Debug Information Format** set to **DWARF with dSYM File** in your Xcode build settings so that every build produces dSYMs. +2. Remove any build phase, script, or CI step that uploads dSYMs to Sauce Labs Mobile App Distribution with your upload API key. +3. Upload the dSYMs to Backtrace instead, as described in [Upload Debug Symbols](/error-reporting/platform-integrations/ios/setup/#upload-debug-symbols) and [Symbolication](/error-reporting/project-setup/symbolication/). + +**Verify.** A crash report in Backtrace shows your method names instead of addresses. + +## 6. Add the Correlation Attributes + +To match a Backtrace crash report with the session recorded up to the crash, both SDKs must carry the same `sauce.correlation_id` attribute: one lowercase UUID v4 generated per app launch before either SDK starts, together with the other reserved attributes. Register a session state delegate before `beginWithoutCrashHandler` to copy the session URL into Backtrace on every session start. + +The contract is defined once, in [Using Sauce Mobile Beta with Backtrace](/app-distribution/sdk/backtrace-coexistence), which has the initialization order and a complete `AppDelegate` example in Swift, and [Reserved Attributes for Backtrace Coexistence](/app-distribution/sdk/session-attributes#reserved-attributes-for-backtrace-coexistence). + +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](/app-distribution/sdk/identifying-users). + +**Verify.** A session's attributes in Sauce Labs Mobile App Distribution show `sauce.correlation_id`, and a Backtrace report from the same launch shows the same value. + +## 7. Validate Before Release + +1. The dependency check from step 1 prints nothing, and `Package.resolved` lists `testfairy-ios-sdk-swift-package` at `2.2.0-rc` only. +2. A clean build succeeds. A duplicate module or duplicate symbol error for `TestFairy` means a TestFairy SDK 1.x artifact is still linked. Go back to step 1. +3. 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. +4. Submit feedback from the app and confirm that the session shows `sauce.correlation_id` in its attributes, with the same value as the Backtrace report from that launch. +5. Confirm that store builds do not contain the Sauce Mobile Beta SDK while Backtrace stays. See [Sauce Mobile Beta SDK in Production](/app-distribution/sdk/tf-production). + +## Troubleshooting + +**The package does not resolve.** The **Up to Next Major Version** rule skips the `2.2.0-rc` pre-release. Set the **Dependency Rule** to **Exact Version** `2.2.0-rc`. + +**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](/error-reporting/platform-integrations/ios/setup/). + +**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. diff --git a/docs/app-distribution/sdk/migration/overview.md b/docs/app-distribution/sdk/migration/overview.md new file mode 100644 index 0000000000..a02ff2b538 --- /dev/null +++ b/docs/app-distribution/sdk/migration/overview.md @@ -0,0 +1,109 @@ +--- +id: overview +title: Migrating from the TestFairy SDK +sidebar_label: Overview +description: Learn what changes when you move an app from the TestFairy SDK 1.x to the Sauce Mobile Beta SDK, and find the steps for each platform. +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; + +

Beta release

+ +:::info Beta release +The Sauce Mobile Beta SDK is in beta. The current release candidates are 2.2.0-rc for iOS and Android and 3.0.0-rc for React Native. + +- Final: the artifact names and the API. +- Can still change before general availability: the version numbers, the iOS package URL, and this documentation. + +Share feedback with your Sauce Labs representative. +::: + +This guide is for developers whose app already ships the TestFairy SDK 1.x (on React Native, the `react-native-testfairy` 2.x package) and who want to move it to the Sauce Mobile Beta SDK. If you are adding the SDK to an app for the first time, start with [Adding the Sauce Mobile Beta SDK to your App](/app-distribution/sdk/adding-tf-sdk) instead. + +The migration is a dependency swap plus a small set of code changes. The pages in this section walk through it step by step for each platform: + +- [Migrating an iOS App from the TestFairy SDK](/app-distribution/sdk/migration/ios) +- [Migrating an Android App from the TestFairy SDK](/app-distribution/sdk/migration/android) +- [Migrating a React Native App from the TestFairy SDK](/app-distribution/sdk/migration/react-native) + +## What Stays the Same + +The runtime API keeps its TestFairy names, so your existing code keeps compiling: + +- iOS: the module and class are `TestFairy`. You keep `import TestFairy` in Swift and `#import "TestFairy.h"` in Objective-C. +- Android: the Java package is `com.testfairy`. You keep `import com.testfairy.TestFairy;` and the `TestFairy.*` calls. +- React Native: the default export is `TestFairy`, the native module is `TestFairyBridge`, and the JavaScript API is the same. Only the package name changes. +- `begin(...)` keeps compiling and starts a session. It no longer installs a crash handler. +- Session recording, feedback, remote logging, events, user identification, and session attributes work as before. + +## What Changes + +| Area | TestFairy SDK 1.x | Sauce Mobile Beta SDK | +| :--- | :--- | :--- | +| iOS artifact | CocoaPods `pod 'TestFairy'`, Carthage binary from `https://app.testfairy.com/sdk/ios/carthage.json`, or a manually added `TestFairySDK.framework` | Swift package `https://github.com/testfairy/testfairy-ios-sdk-swift-package`, product `SauceMobileBeta`, module `TestFairy`, exact version `2.2.0-rc`. The CocoaPods pod `SauceMobileBeta` is not published yet. | +| Android artifact | `com.testfairy:testfairy-android-sdk` and `com.testfairy:testfairy-android-ndk` | `com.saucelabs.mobilebeta:sauce-mobile-beta-android:2.2.0-rc` from `https://maven.testfairy.com` | +| React Native package | `react-native-testfairy` 2.x | `@saucelabs/mobile-beta-react-native` 3.0.0-rc, installed from the GitHub Release tarball until npm publishing is on | +| Entry point | `begin(...)`, often preceded by `enableCrashHandler` or `disableCrashHandler` | `beginWithoutCrashHandler(...)`. It starts a session, never installs a crash handler, and forces the `enableCrashReporter` option (`TFSDKEnableCrashReporterKey` on iOS) to false even if you pass true. | +| Crash reporting | Captured by the SDK's own crash handler and shown in Sauce Labs Mobile App Distribution | Provided by Backtrace (Sauce Labs Error Reporting). An app on the Sauce Mobile Beta SDK sends no crash reports to Sauce Labs Mobile App Distribution, so no crash reports or dSYM symbolication appear there for it. | +| Crash history | `didLastSessionCrash` | Always `false`. Use Backtrace for crash history. | +| Minimum Android version | API level 16 | API level 16 for the SDK alone, API level 21 when Backtrace is in the same app | + +## One Crash Owner + +A process has one owner of its signal and exception handlers. The TestFairy SDK 1.x installed its own crash handler, so it could not share an app with Backtrace: whichever SDK initialized last took over, and crashes were lost or misattributed. The Sauce Mobile Beta SDK does not install a crash handler at any layer, on any platform, so Backtrace is always the sole crash owner. + +Two rules follow from this: + +- Never mix the two artifact families. Both contain the same `TestFairy` classes (`com.testfairy` on Android, the `TestFairy` module on iOS, the `TestFairyBridge` module in React Native), so a build that contains both fails with duplicate class, duplicate module, or duplicate symbol errors, and the TestFairy SDK 1.x artifact can bring a crash handler back. +- Never run the TestFairy SDK 1.x beside Backtrace. The two compete for the same crash handlers. + +Remove every TestFairy SDK 1.x artifact first, then add the Sauce Mobile Beta artifact, then add Backtrace. The platform pages name every artifact and install channel to remove. + +## Crash API Behavior + +The crash entry points remain in the API, so code that calls them keeps compiling, but they do nothing. Delete them from your code when you migrate. Nothing breaks if they stay. + +| API | TestFairy SDK 1.x behavior | Sauce Mobile Beta SDK behavior | +| :--- | :--- | :--- | +| `installCrashHandler` | Installs the crash handler without a session | No-op. Android logs a warning to logcat, and iOS records the message in the SDK's remote log. No session is started. | +| `enableCrashHandler` | Enables crash capture before `begin` | No-op (logged) | +| `disableCrashHandler` | Disables crash capture before `begin` | No-op (the SDK never installs a crash handler) | +| `didLastSessionCrash` | Whether the previous session crashed | Always `false`. Query Backtrace for crash history instead. | +| `enableCrashReporter` option (`TFSDKEnableCrashReporterKey` on iOS) | Turns crash capture on or off | Forced to `false` by `beginWithoutCrashHandler`. `begin` also starts a session without a crash handler. | +| React Native `isCrashReportingAvailable()` | Not present in `react-native-testfairy` 2.x | Returns `false` | +| React Native `getIntegrationInfo()` | Not present in `react-native-testfairy` 2.x | Returns `{ sdkName, crashReportingAvailable: false, coexistenceMode: 'backtrace_crash_owner' }` | + +Do not try to chain a TestFairy uncaught-exception handler with Backtrace's. The Sauce Mobile Beta SDK is built without one. + +## Deprecated Identity Methods + +`setCorrelationId` and `identify` are deprecated. They write the same user-identity field that `setUserId` writes (on Android, only the first call per session takes effect), so they overwrite, or are overwritten by, your real user ID. + +- Replace `identify(id, traits)` with `setUserId(id)` plus one `setAttribute` call per trait. +- Do not use `setCorrelationId` or `identify` for the Backtrace correlation id. The value that links a Backtrace crash report to a Sauce Mobile Beta session is the session attribute `sauce.correlation_id`, set with `setAttribute` before the session starts. + +See [Identifying Your Users](/app-distribution/sdk/identifying-users) and [Session Attributes](/app-distribution/sdk/session-attributes). + +## Migration Checklist + +Each platform page covers the same stages with platform-specific commands and a verification step for each stage: + +1. Remove the TestFairy SDK 1.x artifacts from every install channel. +2. Add the Sauce Mobile Beta artifact and pin the exact version. +3. Update the initialization to `beginWithoutCrashHandler`. +4. Delete the crash-handler calls and set up Backtrace for crash reporting. +5. Point symbol uploads (dSYMs, ProGuard mappings, native symbols) at Backtrace. +6. Add the correlation attributes so a crash report and its session can be matched. +7. Validate before release: inspect the dependency graph, force a crash, and check a feedback session. + +Follow the page for your platform: + +- [iOS](/app-distribution/sdk/migration/ios) +- [Android](/app-distribution/sdk/migration/android) +- [React Native](/app-distribution/sdk/migration/react-native) (also applies to Expo projects with prebuild) + +## After You Migrate + +Backtrace owns crashes and the Sauce Mobile Beta SDK owns beta sessions. To match a crash report with the session recorded up to the crash, both SDKs must carry the same `sauce.correlation_id` attribute, together with the other reserved attributes. The initialization order, the attribute contract, and complete examples for every platform are in [Using Sauce Mobile Beta with Backtrace](/app-distribution/sdk/backtrace-coexistence) and [Reserved Attributes for Backtrace Coexistence](/app-distribution/sdk/session-attributes#reserved-attributes-for-backtrace-coexistence). + +Keep the Sauce Mobile Beta SDK out of store builds and let Backtrace stay. See [Sauce Mobile Beta SDK in Production](/app-distribution/sdk/tf-production). diff --git a/docs/app-distribution/sdk/migration/react-native.md b/docs/app-distribution/sdk/migration/react-native.md new file mode 100644 index 0000000000..4480312cbc --- /dev/null +++ b/docs/app-distribution/sdk/migration/react-native.md @@ -0,0 +1,231 @@ +--- +id: react-native +title: Migrating a React Native App from the TestFairy SDK +sidebar_label: React Native +description: Move a React Native app from react-native-testfairy 2.x to @saucelabs/mobile-beta-react-native. +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +

Beta release

+ +:::info Beta release +The Sauce Mobile Beta SDK is in beta. The current release candidates are 2.2.0-rc for iOS and Android and 3.0.0-rc for React Native. + +- Final: the artifact names and the API. +- Can still change before general availability: the version numbers, the iOS package URL, and this documentation. + +Share feedback with your Sauce Labs representative. +::: + +Follow these steps to move a React Native app from the `react-native-testfairy` 2.x package to `@saucelabs/mobile-beta-react-native` 3.0.0-rc, the Sauce Mobile Beta SDK for React Native. Read [Migrating from the TestFairy SDK](/app-distribution/sdk/migration/overview) first for what changes and why. For the full install reference of the new package, see [Integrating the React Native SDK](/app-distribution/sdk/react-native/react-native) and, for Expo projects with prebuild, [Expo (with prebuild)](/app-distribution/sdk/react-native/expo). + +The JavaScript API is the same: the default export is `TestFairy` and the native module is `TestFairyBridge`. The migration is a package swap, one import change, and one initialization change. The package bundles the native Sauce Mobile Beta SDKs 2.2.0-rc for iOS and Android, so you do not add the native SDKs yourself. + +Requirements: React Native 0.71 or later, React 18 or later, Node.js 18 or later, iOS 12.4 or later (the React Native 0.71 minimum; the package's podspec requires iOS 12.0), and Android API level 21 or later (required by React Native 0.71 and by Backtrace (Sauce Labs Error Reporting)). + +:::caution Order matters +The two packages are mutually exclusive. `react-native-testfairy` 2.x exposes the same `TestFairyBridge` native module and the same `com.testfairy` native classes as `@saucelabs/mobile-beta-react-native`, so a project that contains both fails with duplicate module and duplicate class errors. `react-native-testfairy` also installs its own crash handler, so never run it beside Backtrace: a process has one owner of its signal and exception handlers. +::: + +## 1. Remove the TestFairy SDK Artifacts + +Uninstall the package: + +```bash +npm uninstall react-native-testfairy +``` + +Then remove the native pieces that an earlier integration may have added by hand: + +- iOS: a `pod 'TestFairy'` line in `ios/Podfile`, and a manually linked `TestFairySDK.framework` or `libTestFairy.a` in the Xcode project. The `React-TestFairy` pod and `libTestFairy.a` that the old package bundled go away with the uninstall. +- Android: `com.testfairy:testfairy-android-sdk` and `com.testfairy:testfairy-android-ndk` in any `build.gradle`. +- Android: a manually registered `TestFairyPackage` in `MainApplication` (the `new TestFairyPackage()` entry in the packages list and its `import com.testfairy.react.TestFairyPackage;` line). React Native 0.60 and later autolink the package, so no manual registration is needed. + +**Verify.** `npm ls react-native-testfairy` reports `(empty)` under your project name, and the grep prints nothing: + +```bash +npm ls react-native-testfairy +grep -rn "react-native-testfairy\|TestFairyPackage\|com.testfairy:testfairy" ios/Podfile android --include="*.gradle" --include="*.java" --include="*.kt" --include="Podfile" --exclude-dir=build --exclude-dir=.gradle +``` + +## 2. Add the Sauce Mobile Beta Package + +Until npm publishing is switched on, install the package from the tarball attached to the [GitHub Release](https://github.com/testfairy/react-native-testfairy/releases/tag/3.0.0-rc). It installs into `node_modules/@saucelabs/mobile-beta-react-native` and autolinks exactly like a registry install: + +```bash +npm install https://github.com/testfairy/react-native-testfairy/releases/download/3.0.0-rc/saucelabs-mobile-beta-react-native-3.0.0-rc.tgz +``` + +:::note +After the package is on npm, install it with `npm install @saucelabs/mobile-beta-react-native` instead of the release asset URL. The package name, version scheme, and API stay the same. +::: + +**Verify.** `package.json` lists `@saucelabs/mobile-beta-react-native`, and `npm ls @saucelabs/mobile-beta-react-native` prints version `3.0.0-rc`. + +## 3. Update the Import and the Initialization + +Update the import everywhere the old package was imported. The runtime class is still `TestFairy`, so no other source changes are needed: + +```js +// Before +import TestFairy from 'react-native-testfairy'; +// After +import TestFairy from '@saucelabs/mobile-beta-react-native'; +``` + +Replace the old initialization, `disableCrashHandler()` followed by `begin(token)`, with `beginWithoutCrashHandler(token)`. It starts a session, never installs a crash handler, and forces the `enableCrashReporter` option to `false` even if you pass `true`. It accepts the same optional second argument as `begin` for session options. Call it once per app launch from your root component, with the app token from the [user preferences](https://app.testfairy.com/settings/) on your Sauce Labs Mobile App Distribution account: + +```js +import React, { useEffect } from 'react'; +import TestFairy from '@saucelabs/mobile-beta-react-native'; + +export default function App() { + useEffect(() => { + // Before: + // TestFairy.disableCrashHandler(); + // TestFairy.begin(''); + + // After. Initialize Backtrace before this line when it is part of the app. + TestFairy.beginWithoutCrashHandler(''); + }, []); + + return ; // your root component tree +} +``` + +**Verify.** The following command prints nothing: + +```bash +grep -rn "from 'react-native-testfairy'\|require('react-native-testfairy')" --include="*.js" --include="*.jsx" --include="*.ts" --include="*.tsx" --exclude-dir=node_modules . +``` + +## 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: + +```js +// Delete these calls. They are no-ops in the Sauce Mobile Beta SDK. +TestFairy.enableCrashHandler(); +TestFairy.disableCrashHandler(); +``` + +Two new calls describe the behavior: `isCrashReportingAvailable()` returns `false`, and `getIntegrationInfo()` returns `{ sdkName, crashReportingAvailable: false, coexistenceMode: 'backtrace_crash_owner' }`. + +Crash reporting is provided by Backtrace, which owns JavaScript unhandled errors and unhandled promise rejections, iOS native crashes, and Android JVM and native crashes. Add `@backtrace/react-native` by following the [Backtrace React Native integration guide](/error-reporting/language-integrations/react-native/), and initialize it before `beginWithoutCrashHandler` so that it is the only crash owner. New to Backtrace? Start with [Getting Started](/error-reporting/getting-started/). + +**Verify.** The following command prints nothing: + +```bash +grep -rn "enableCrashHandler\|disableCrashHandler" --include="*.js" --include="*.jsx" --include="*.ts" --include="*.tsx" --exclude-dir=node_modules --exclude-dir=ios --exclude-dir=android . +``` + +## 5. Update the Native Projects + +**iOS.** The package's podspec vendors the `TestFairy` xcframework, so you do not add a pod for the native SDK yourself. Reinstall the pods from a clean state: + +```bash +cd ios && rm -rf Pods Podfile.lock && pod install +``` + +Run `pod install` again after every install or upgrade of the package. + +**Android.** The package pins the native artifact `com.saucelabs.mobilebeta:sauce-mobile-beta-android:2.2.0-rc` and ships it in a local Maven repository (`local-native/android/maven`), which its `build.gradle` registers on every Gradle project together with `https://maven.testfairy.com` as a fallback. A standard React Native project needs no repository change. If your `android/settings.gradle` (or `settings.gradle.kts`) restricts repositories with `dependencyResolutionManagement` and `RepositoriesMode.FAIL_ON_PROJECT_REPOS`, declare the fallback repository there as well. The `content` filter is optional and limits the repository to the Sauce Mobile Beta group: + + + + + +```groovy +// android/settings.gradle +dependencyResolutionManagement { + repositories { + google() + mavenCentral() + + maven { + url "https://maven.testfairy.com" + content { includeGroup "com.saucelabs.mobilebeta" } + } + } +} +``` + + + + + +```kotlin +// android/settings.gradle.kts +dependencyResolutionManagement { + repositories { + google() + mavenCentral() + + maven { + url = uri("https://maven.testfairy.com") + content { includeGroup("com.saucelabs.mobilebeta") } + } + } +} +``` + + + + +The native artifact bundles its consumer ProGuard rules (`-keep class com.testfairy.** { *; }` and `-dontwarn com.testfairy.**`), so existing keep rules remain valid. When Backtrace is in the app, set `minSdk 21`. + +**Clean the native build directories** before rebuilding, so that no output from the old package survives: + +```bash +rm -rf ios/build android/.gradle android/build android/app/build +``` + +:::note Expo (with prebuild) +The package contains native code, so it does not run in Expo Go. Your project needs generated `ios` and `android` directories (`npx expo prebuild`). `npx expo prebuild --clean` regenerates the native directories, so re-apply the Maven repository change (or apply it with a config plugin) whenever you regenerate them. See [Expo (with prebuild)](/app-distribution/sdk/react-native/expo). +::: + +**Verify.** `ios/Podfile.lock` lists neither `React-TestFairy` nor the `TestFairy` pod it depended on, and the Android dependency graph lists nothing from the `com.testfairy` group: + +```bash +grep -n "TestFairy" ios/Podfile.lock +cd android && ./gradlew :app:dependencies --configuration debugRuntimeClasspath | grep testfairy +``` + +## 6. Add the Correlation Attributes + +To match a Backtrace crash report with the session recorded up to the crash, both SDKs must carry the same `sauce.correlation_id` attribute: one lowercase UUID v4 generated per app launch before either SDK starts, together with the other reserved attributes. Backtrace receives them as `userAttributes` in `BacktraceClient.initialize(...)`, and the Sauce Mobile Beta SDK through `TestFairy.setAttribute(key, value)` before `beginWithoutCrashHandler`. A session state listener registered with `TestFairy.addSessionStateListener(...)` before the start copies the session URL into Backtrace on every session start. + +The contract is defined once, in [Using Sauce Mobile Beta with Backtrace](/app-distribution/sdk/backtrace-coexistence), which has the initialization order and a complete React Native example, and [Reserved Attributes for Backtrace Coexistence](/app-distribution/sdk/session-attributes#reserved-attributes-for-backtrace-coexistence). A working setup is in the package repository at [example/src/observability.ts](https://github.com/testfairy/react-native-testfairy/blob/3.0.0-rc/example/src/observability.ts). + +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](/app-distribution/sdk/identifying-users). + +**Verify.** A session's attributes in Sauce Labs Mobile App Distribution show `sauce.correlation_id`, and a Backtrace report from the same launch shows the same value. + +## 7. Validate Before Release + +1. `npm ls react-native-testfairy` reports `(empty)`, `ios/Podfile.lock` lists neither `React-TestFairy` nor `TestFairy`, and the Android dependency graph lists nothing from the `com.testfairy` group. +2. A clean build of both platforms succeeds. A duplicate `TestFairyBridge` module error, a duplicate symbol error for `TestFairy`, or a `Duplicate class` error for `com.testfairy` classes means the old package or an old native artifact is still present. Go back to step 1 and step 5. +3. Run a debug build with both SDKs initialized and force a crash, for example an unhandled JavaScript error and a native crash. The process dies as a genuine crash. Relaunch the app: the crashes appear in Backtrace only, and the Sauce Mobile Beta SDK starts a new session. +4. Submit feedback from the app and confirm that the session shows `sauce.correlation_id` in its attributes, with the same value as the Backtrace report from that launch. +5. Confirm that store builds do not contain the Sauce Mobile Beta SDK while Backtrace stays. See [Sauce Mobile Beta SDK in Production](/app-distribution/sdk/tf-production). + +## Troubleshooting + +**Duplicate `TestFairyBridge` module, duplicate symbols for `TestFairy`, or `Duplicate class` errors for `com.testfairy`.** `react-native-testfairy` or one of the old native artifacts is still present. Run the checks from step 1 and step 5, clean the native build directories, and rebuild. + +**The app crashes at startup with `Native module TestFairyModule tried to override TestFairyModule`.** A manual `new TestFairyPackage()` registration from the old integration is still in `MainApplication`. The new package autolinks and also provides `com.testfairy.react.TestFairyPackage`, so the module is registered twice. Remove the manual entry and its import. + +**`Could not find com.saucelabs.mobilebeta:sauce-mobile-beta-android:2.2.0-rc`.** Confirm that `node_modules/@saucelabs/mobile-beta-react-native/local-native/android/maven` exists, and reinstall the package if it does not. If `android/settings.gradle` sets `RepositoriesMode.FAIL_ON_PROJECT_REPOS`, Gradle rejects the repositories the package registers: declare `https://maven.testfairy.com` in `dependencyResolutionManagement` as shown in step 5. On Expo, re-check after every `npx expo prebuild --clean`. + +**The iOS build still links the old SDK after the uninstall.** Run `cd ios && rm -rf Pods Podfile.lock && pod install` and delete `ios/build`. + +**A crash shows up nowhere.** The Sauce Mobile Beta SDK does not report crashes, and `isCrashReportingAvailable()` returns `false` by design. Make sure Backtrace is initialized before `beginWithoutCrashHandler` and that its submission URL is correct. See the [Backtrace React Native integration guide](/error-reporting/language-integrations/react-native/). diff --git a/docs/app-distribution/sdk/options.md b/docs/app-distribution/sdk/options.md new file mode 100644 index 0000000000..65de377984 --- /dev/null +++ b/docs/app-distribution/sdk/options.md @@ -0,0 +1,299 @@ +--- +id: options +title: Begin with Options +sidebar_label: Begin with Options +description: Override build settings such as video recording, recorded metrics, and session length when you start the Sauce Mobile Beta SDK. +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +

Beta release

+ +The Sauce Mobile Beta SDK requires that you call `beginWithoutCrashHandler` (or `begin`) to start recording your sessions. However, developers can override the build settings to determine what is enabled during a session recording. + +Some commonly used options: + +- Video Recording +- Recorded Metrics +- Max Session Length +- Feedback Form + +:::note Crash reporting +The SDK does not install a crash handler. Crash reporting is provided by Backtrace (Sauce Labs Error Reporting), and the two SDKs are designed to run in the same app. `beginWithoutCrashHandler` starts the session without a crash handler and forces the `enableCrashReporter` option (`TFSDKEnableCrashReporterKey` on iOS) to `false` even if you pass `true` in the options. Initialize Backtrace before the SDK. See [Using Sauce Mobile Beta with Backtrace](/app-distribution/sdk/backtrace-coexistence). +::: + + + + + +## Video Recording + +Sauce Labs Mobile App Distribution provides an option to enable or disable video recording and control the recording parameters. Invoke `disableVideo` or `enableVideo` before `beginWithoutCrashHandler`. + +### Syntax + +```java +TestFairy.disableVideo(); +TestFairy.enableVideo("", "", ); +``` + +Refer to the [Class Reference](https://app.testfairy.com/reference/android/index.html) for more information on `policy` and `quality` values. + +### Code Example + +In the following example, video only records when wifi is available. A high-quality video is recorded every 2 seconds. + +```java +import com.testfairy.TestFairy; + +public class MyApplication extends Application { + @Override + public void onCreate() { + super.onCreate(); + + TestFairy.enableVideo("wifi", "high", 2.0f); + TestFairy.beginWithoutCrashHandler(this, ""); + } +} +``` + +Once logged in, your app token is available from your [account preferences](https://app.testfairy.com/settings#apptoken). + +## Recorded Metrics + +Sauce Labs Mobile App Distribution can collect several different metrics from your app. Developers can override the metrics defined in their app's build settings. + +Developers can call `enableMetric` or `disableMetric` before `beginWithoutCrashHandler` with the metric they wish to enable or disable recording. + +:::note +Any metric that is enabled or disabled override the settings set in your app's build settings. +::: + +### Syntax + +```java +TestFairy.enableMetric(""); +TestFairy.disableMetric(""); +``` + +Refer to the [Class Reference](https://app.testfairy.com/reference/android/index.html) for more information on which metric can be passed. + +### Code Example + +In the following snippet, the CPU metric will be recorded, and the Memory metric will not be recorded, regardless of what's set in the build settings. + +```java +import com.testfairy.TestFairy; + +public class MyApplication extends Application { + @Override + public void onCreate() { + super.onCreate(); + + TestFairy.enableMetric("cpu"); + TestFairy.disableMetric("memory"); + TestFairy.beginWithoutCrashHandler(this, ""); + } +} +``` + +Once logged in, your app token is available from your [account preferences](https://app.testfairy.com/settings#apptoken). + +## Max Session Length + +Sauce Labs Mobile App Distribution only records for a fixed period. Developers can override the maximum recording period by calling `setMaxSessionLength` before `beginWithoutCrashHandler`. + +:::note +The value passed into this method must be less than or equal to the value defined in the build settings. A value larger than the one in the build settings will be ignored. +::: + +### Syntax + +```java +TestFairy.setMaxSessionLength(); +``` + +### Code Example + +```java +import com.testfairy.TestFairy; + +public class MyApplication extends Application { + @Override + public void onCreate() { + super.onCreate(); + + TestFairy.setMaxSessionLength(10 * 60); // Record for 10 minutes + TestFairy.beginWithoutCrashHandler(this, ""); + } +} +``` + +Once logged in, your app token is available from your [account preferences](https://app.testfairy.com/settings#apptoken). + +## Feedback Form + +Sauce Labs Mobile App Distribution provides an option to enable or disable feedback collection. Invoke `disableFeedbackForm` or `enableFeedbackForm` before `beginWithoutCrashHandler`. + +### Syntax + +```java +TestFairy.disableFeedbackForm(); +TestFairy.enableFeedbackForm(""); +``` + +Refer to the [Class Reference](https://app.testfairy.com/reference/android/index.html) for more information on values for `method`. + +### Code Example + +In the following example, feedback will be enabled when the device is shaken. + +```java +import com.testfairy.TestFairy; + +public class MyApplication extends Application { + @Override + public void onCreate() { + super.onCreate(); + + TestFairy.enableFeedbackForm("shake"); + TestFairy.beginWithoutCrashHandler(this, ""); + } +} +``` + +Once logged in, your app token is available from your [account preferences](https://app.testfairy.com/settings#apptoken). + + + + + +## Video Recording + +The Sauce Mobile Beta SDK provides an option to enable or disable video recording and control the recording parameters. Invoke `disableVideo` or `enableVideo` before `beginWithoutCrashHandler`. + +### Syntax + +```objectivec +[TestFairy disableVideo]; +[TestFairy enableVideo:@"" quality:@"" framesPerSecond:]; +``` + +Refer to the [Class Reference](https://app.testfairy.com/reference/ios/Classes/TestFairy.html) for more information on `policy` and `quality` values. + +### Code Example + +In the following example, the video will only be recorded when wifi is available. A high-quality video will be recorded every 2 seconds. + +```objectivec +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + [TestFairy enableVideo:@"wifi" quality:@"high" framesPerSecond:2.0]; + [TestFairy beginWithoutCrashHandler:@""]; +} +``` + +Once logged in, your app token is available from your [account preferences](https://app.testfairy.com/settings#apptoken). + +## Recorded Metrics + +Sauce Labs Mobile App Distribution can collect several different metrics from your app. Developers can override the metrics defined in their app's build settings. + +Developers can call `enableMetric` or `disableMetric` before `beginWithoutCrashHandler` with the metric they wish to enable or disable recording. + +:::note +Any metric that is enabled or disabled will override the settings set in your app's build settings. +::: + +### Syntax + +```objectivec +[TestFairy enableMetric:@""]; +[TestFairy disableMetric:@""]; +``` + +Refer to the [Class Reference](https://app.testfairy.com/reference/ios/Classes/TestFairy.html) for more information on which metric can be passed. + +### Code Example + +In the following snippet, the CPU metric will be recorded, and the Memory metric will not be recorded, regardless of what's set in the build settings. + +```objectivec +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + [TestFairy enableMetric:@"cpu"]; + [TestFairy disableMetric:@"memory"]; + [TestFairy beginWithoutCrashHandler:@""]; + // ... +} +``` + +Once logged in, your app token is available from your [account preferences](https://app.testfairy.com/settings#apptoken). + +## Max Session Length + +The Sauce Mobile Beta SDK only records for a fixed period. Developers can override the maximum recording period by calling `setMaxSessionLength` before `beginWithoutCrashHandler`. + +:::note +The value passed into this method must be less than or equal to the value defined in the build settings. A value larger than the one in the build settings will be ignored. +::: + +### Syntax + +```objectivec +[TestFairy setMaxSessionLength:]; +``` + +### Code Example + +```objectivec +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + [TestFairy setMaxSessionLength:(10 * 60)]; // Record for 10 minutes + [TestFairy beginWithoutCrashHandler:@""]; +} +``` + +Once logged in, your app token is available from your [account preferences](https://app.testfairy.com/settings#apptoken). + +## Feedback Form + +The Sauce Mobile Beta SDK provides an option to enable or disable feedback collection. Invoke `disableFeedbackForm` or `enableFeedbackForm` before `beginWithoutCrashHandler`. + +### Syntax + +```objectivec +[TestFairy disableFeedbackForm]; +[TestFairy enableFeedbackForm:@""]; +``` + +Refer to the [Class Reference](https://app.testfairy.com/reference/ios/Classes/TestFairy.html) for more information on values for `method`. + +### Code Example + +In the following example, feedback will be enabled when the device is shaken. + +```objectivec +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + [TestFairy enableFeedbackForm:@"shake|screenshot"]; + [TestFairy beginWithoutCrashHandler:@""]; +} +``` + +Once logged in, your app token is available from your [account preferences](https://app.testfairy.com/settings#apptoken). + + + diff --git a/docs/app-distribution/sdk/private-cloud-int.md b/docs/app-distribution/sdk/private-cloud-int.md new file mode 100644 index 0000000000..3da65ffe63 --- /dev/null +++ b/docs/app-distribution/sdk/private-cloud-int.md @@ -0,0 +1,83 @@ +--- +id: private-cloud-int +title: Private Cloud Integration +sidebar_label: Private Cloud Integration +description: Point the Sauce Mobile Beta SDK at your Mobile App Distribution Private Cloud endpoint. +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +

Beta release

+ +If your organization runs Sauce Labs Mobile App Distribution on a [Private Cloud](/app-distribution/security/private-cloud), point the Sauce Mobile Beta SDK at your Private Cloud endpoint so that session data goes to your dedicated instance. + +## Setting Your Endpoint + +Once your private cloud is set up, get the URL endpoint your app will direct all of its data towards. This URL must be passed into the SDK before the `begin` method is called. + + + + + +```js +TestFairy.setServerEndpoint(""); +``` + +Example + +```js +// Be sure to import the SDK +import com.testfairy.TestFairy; + +TestFairy.setServerEndpoint("my-subdomain.testfairy.com"); +TestFairy.beginWithoutCrashHandler(context, ""); +``` + + + + + +```js +[TestFairy setServerEndpoint:@""]; +``` + +Example + +```js +// Be sure to import the SDK +#import "TestFairy.h" + +[TestFairy setServerEndpoint:@"my-subdomain.testfairy.com"]; +[TestFairy beginWithoutCrashHandler:@""]; +``` + + + + + +```js +TestFairy.setServerEndpoint(""); +``` + +Example + +```js +// Be sure to import the SDK +import TestFairy from '@saucelabs/mobile-beta-react-native'; + +TestFairy.setServerEndpoint("my-subdomain.testfairy.com"); +TestFairy.beginWithoutCrashHandler(""); +``` + + + + diff --git a/docs/app-distribution/sdk/react-native/expo.md b/docs/app-distribution/sdk/react-native/expo.md new file mode 100644 index 0000000000..dff50984ab --- /dev/null +++ b/docs/app-distribution/sdk/react-native/expo.md @@ -0,0 +1,66 @@ +--- +id: expo +title: Expo (with prebuild) +sidebar_label: Expo (with prebuild) +description: Add the Sauce Mobile Beta SDK for React Native to an Expo project that generates its native projects with prebuild. +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +

Beta release

+ +:::info Beta release +The Sauce Mobile Beta SDK is in beta. The current release candidates are 2.2.0-rc for iOS and Android and 3.0.0-rc for React Native. + +- Final: the artifact names and the API. +- Can still change before general availability: the version numbers, the iOS package URL, and this documentation. + +Share feedback with your Sauce Labs representative. +::: + +[Sauce Mobile Beta for React Native](https://github.com/testfairy/react-native-testfairy/releases/tag/3.0.0-rc) (`@saucelabs/mobile-beta-react-native`) is a bridge to the Sauce Mobile Beta SDK and is supported for Expo (with prebuild): Expo projects whose native `ios` and `android` directories are generated with `npx expo prebuild`. Integrating the SDK into your app shows you how your app performs on real devices. It shows when and how people use your app and provides any metrics you may need to optimize your user experience and code. + +## Requirements + +The package contains native iOS and Android code, so it does not run in Expo Go. Your project needs generated `ios` and `android` directories before you install it. Generate them with `npx expo prebuild`, which Expo calls Continuous Native Generation. See the [Expo prebuild docs](https://docs.expo.dev/workflow/prebuild/). + +The SDK does not install a crash handler. Crash reporting is provided by Backtrace (Sauce Labs Error Reporting), and the two SDKs are designed to run in the same app. Start the SDK with `beginWithoutCrashHandler`. See [Crash Ownership](/app-distribution/sdk/react-native/react-native#crash-ownership). + +## Steps + +From your project root, run the following commands. Until npm publishing is switched on, the package is installed from the tarball attached to the GitHub Release: + +```bash +npx expo prebuild +npm install https://github.com/testfairy/react-native-testfairy/releases/download/3.0.0-rc/saucelabs-mobile-beta-react-native-3.0.0-rc.tgz + +cd ios +pod install +``` + +:::note +After the package is published on npm, install it with `npm install @saucelabs/mobile-beta-react-native` instead of the release asset URL. +::: + +On Android, the package registers its Maven repositories on every Gradle project, so a standard Expo project needs no repository change. If your `android/settings.gradle` restricts repositories with `RepositoriesMode.FAIL_ON_PROJECT_REPOS`, add the Sauce Mobile Beta Maven repository as shown in [Android](/app-distribution/sdk/react-native/react-native#android), and re-apply that change (or apply it with a config plugin) after every `npx expo prebuild --clean`, which regenerates the native directories. + +Then start the SDK once per app launch from your root component, using the app token from the [user preferences](https://app.testfairy.com/settings/) on your Sauce Labs Mobile App Distribution account: + +```js +import React, { useEffect } from 'react'; +import TestFairy from '@saucelabs/mobile-beta-react-native'; + +export default function App() { + useEffect(() => { + TestFairy.beginWithoutCrashHandler(''); + }, []); + + return ; // your root component tree +} +``` + +## Notes + +Since all Expo apps are React Native apps behind the scenes, everything on the [Integrating the React Native SDK](/app-distribution/sdk/react-native/react-native) page also applies to Expo: the API notes and the [Using With Backtrace](/app-distribution/sdk/react-native/react-native#using-with-backtrace) summary. If you use Backtrace in the same app, initialize Backtrace first and share the `sauce.correlation_id` attribute with both SDKs as described in [Using Sauce Mobile Beta with Backtrace](/app-distribution/sdk/backtrace-coexistence). diff --git a/docs/app-distribution/sdk/react-native/react-native.md b/docs/app-distribution/sdk/react-native/react-native.md new file mode 100644 index 0000000000..23c7ba0a7e --- /dev/null +++ b/docs/app-distribution/sdk/react-native/react-native.md @@ -0,0 +1,184 @@ +--- +id: react-native +title: Integrating the React Native SDK +sidebar_label: Integrating React Native SDK +description: Add the Sauce Mobile Beta SDK for React Native, @saucelabs/mobile-beta-react-native, to your React Native app. +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +

Beta release

+ +:::info Beta release +The Sauce Mobile Beta SDK is in beta. The current release candidates are 2.2.0-rc for iOS and Android and 3.0.0-rc for React Native. + +- Final: the artifact names and the API. +- Can still change before general availability: the version numbers, the iOS package URL, and this documentation. + +Share feedback with your Sauce Labs representative. +::: + +Sauce Labs Mobile App Distribution lets you distribute beta builds to testers and see how they use your app. The Sauce Mobile Beta SDK for React Native, `@saucelabs/mobile-beta-react-native`, adds session recording, tester feedback, screenshots, remote logging, and session events to your React Native app, so you can identify and debug issues more effectively and optimize the user experience. + +The package is a bridge to the native Sauce Mobile Beta SDKs for iOS and Android. + +## Crash Ownership + +The SDK does not install a crash handler: neither the JavaScript layer, the native bridges, nor the bundled native SDKs do. Crash reporting is provided by Backtrace (Sauce Labs Error Reporting), which owns JavaScript unhandled errors and unhandled promise rejections, iOS native crashes, and Android JVM and native crashes. The two SDKs are designed to run in the same app. + +Start the SDK with `beginWithoutCrashHandler(...)`. It starts a session and never installs a crash handler, and it forces the `enableCrashReporter` option to `false` even if you pass `true`. `begin(...)` also starts a session without a crash handler. Use `beginWithoutCrashHandler` to make the intent explicit in your code. See [Using With Backtrace](#using-with-backtrace) for the full setup. + +## Requirements + +- React Native 0.71 or later and React 18 or later (the package's peer dependencies). Node.js 18 or later is required to install it. The package autolinks, so no manual native linking is needed. +- iOS 12.4 or later, the React Native 0.71 minimum. The package's podspec requires iOS 12.0. +- Android API level 21 or later, required by React Native 0.71 and by Backtrace. + +## Installation + +The current release is `@saucelabs/mobile-beta-react-native` 3.0.0-rc, which bundles the native SDKs 2.2.0-rc for iOS and Android. Until npm publishing is switched on, install the package from the tarball attached to the [GitHub Release](https://github.com/testfairy/react-native-testfairy/releases/tag/3.0.0-rc): + +```bash +npm install https://github.com/testfairy/react-native-testfairy/releases/download/3.0.0-rc/saucelabs-mobile-beta-react-native-3.0.0-rc.tgz +cd ios && pod install +``` + +The package installs into `node_modules/@saucelabs/mobile-beta-react-native` and autolinks exactly like a registry install. + +:::note +Publishing to the npm registry follows. After the package is on npm, install it with `npm install @saucelabs/mobile-beta-react-native` instead of the release asset URL. The package name, version scheme, and API are the same for both install methods. GA versions ship with the same coordinates. +::: + +### iOS + +Run `pod install` in your `ios` directory after every install or upgrade of the package. The package's podspec vendors the `TestFairy` xcframework, so you do not add a pod for the native SDK yourself. + +### Android + +The package pins the native artifact `com.saucelabs.mobilebeta:sauce-mobile-beta-android:2.2.0-rc` and ships it in a local Maven repository, which its `build.gradle` registers on every Gradle project together with `https://maven.testfairy.com` as a fallback. A standard React Native project needs no repository change. If your `settings.gradle` (or `settings.gradle.kts`) restricts repositories with `dependencyResolutionManagement` and `RepositoriesMode.FAIL_ON_PROJECT_REPOS`, declare the fallback repository there as well: + + + + + +```kotlin +// settings.gradle.kts +dependencyResolutionManagement { + repositories { + google() + mavenCentral() + + maven { + url = uri("https://maven.testfairy.com") + content { includeGroup("com.saucelabs.mobilebeta") } + } + } +} +``` + + + + + +```groovy +// settings.gradle +dependencyResolutionManagement { + repositories { + google() + mavenCentral() + + maven { + url "https://maven.testfairy.com" + content { includeGroup "com.saucelabs.mobilebeta" } + } + } +} +``` + + + + +The `content` filter is optional. It limits the repository to the Sauce Mobile Beta group. Do not use version wildcards for pre-release versions. The native artifact bundles its consumer ProGuard rules (`-keep class com.testfairy.** { *; }` and `-dontwarn com.testfairy.**`), so you do not need to add keep rules for it. + +## Usage + +After installing the package, enable session recording in your app. You need your app token, which you can get from the [user preferences](https://app.testfairy.com/settings/) on your Sauce Labs Mobile App Distribution account. + +1. Obtain your app token from the [user preferences](https://app.testfairy.com/settings/) page. +2. Import the SDK in your root component (for example, `App.js` or `App.tsx`) and call `beginWithoutCrashHandler` once per app launch. In a function component, do this in a `useEffect` with an empty dependency array: + +```js +import React, { useEffect } from 'react'; +import TestFairy from '@saucelabs/mobile-beta-react-native'; + +export default function App() { + useEffect(() => { + TestFairy.beginWithoutCrashHandler(''); + }, []); + + return ; // your root component tree +} +``` + +`beginWithoutCrashHandler` accepts the same optional second argument as `begin` for session options. Whatever you pass, `enableCrashReporter` is forced to `false`. Call `setUserId` and `setAttribute` before starting the session so the SDK attaches them to every session it starts. See [Identifying Users](/app-distribution/sdk/identifying-users) and [Session Attributes](/app-distribution/sdk/session-attributes). + +## Using With Backtrace + +Sauce Mobile Beta and [Backtrace](/error-reporting/getting-started/) run side by side in one app: initialize Backtrace first, so it is the sole crash owner, then start Sauce Mobile Beta with `beginWithoutCrashHandler`. Both SDKs receive the same shared attributes (`sauce.correlation_id`, a lowercase UUID v4 generated once per app launch, plus `sauce.sdk.coexistence_mode`, `sauce.environment`, `sauce.release`, and `sauce.dist`): Backtrace as `userAttributes`, Sauce Mobile Beta through `TestFairy.setAttribute(key, value)` before `beginWithoutCrashHandler`. A session state listener registered before the start overwrites the Backtrace attributes `sauce.mobile_beta.session_url` and `sauce.mobile_beta.session_started` on every session start, because one launch can produce several sessions. + +React Native specifics: + +- Install the UUID generator and its polyfill with `npm install uuid react-native-get-random-values`. Neither is a dependency of the package. Import `react-native-get-random-values` before `uuid`. +- The shared attributes go into `BacktraceClient.initialize(...)` as `userAttributes`. +- `TestFairy.addSessionStateListener(...)` returns a subscription. Call `remove()` on it if you tear the integration down. +- Index `sauce.correlation_id` in Backtrace (Project Settings > Attributes, UUID format) before you filter or group on it. See [Attributes](/error-reporting/project-setup/attributes/). + +```ts +import 'react-native-get-random-values'; // must come before uuid on React Native +import { v4 as uuidv4 } from 'uuid'; +import { BacktraceClient } from '@backtrace/react-native'; +import TestFairy from '@saucelabs/mobile-beta-react-native'; + +const shared: Record = { + 'sauce.correlation_id': uuidv4(), // one lowercase UUID v4 per app launch + 'sauce.sdk.coexistence_mode': 'backtrace_crash_owner', + 'sauce.environment': 'beta', + 'sauce.release': '@', + 'sauce.dist': '', +}; +const backtrace = BacktraceClient.initialize({ // 1. Backtrace first: sole crash owner + url: 'https://submit.backtrace.io///json', + userAttributes: shared, // add your database options (see the Backtrace guide) +}); +const subscription = TestFairy.addSessionStateListener({ // 2. before the start; runs on every session start + onSessionStarted({ sessionUrl }) { + backtrace.addAttribute({ 'sauce.mobile_beta.session_started': 'true', 'sauce.mobile_beta.session_url': sessionUrl ?? '' }); + }, + onSessionFailed() { backtrace.addAttribute({ 'sauce.mobile_beta.session_started': 'false' }); }, +}); +Object.entries(shared).forEach(([key, value]) => TestFairy.setAttribute(key, value)); // 3. same attributes +TestFairy.beginWithoutCrashHandler(''); // 4. start without a crash handler +``` + +For the complete contract, including the iOS and Android specifics, see [Using Sauce Mobile Beta with Backtrace](/app-distribution/sdk/backtrace-coexistence). For the Backtrace side of the setup, see the [Backtrace React Native integration guide](/error-reporting/language-integrations/react-native/). A working setup that shares all attributes is in the package repository at [example/src/observability.ts](https://github.com/testfairy/react-native-testfairy/blob/3.0.0-rc/example/src/observability.ts). + +## API Notes + +The default export is `TestFairy`, the native module is `TestFairyBridge`, and the Android runtime package is `com.testfairy`. + +- `beginWithoutCrashHandler(appToken, options?)` starts a session. See [Crash Ownership](#crash-ownership). +- `isCrashReportingAvailable()` returns `false`. Use Backtrace for crash reporting and crash testing. +- `pushFeedbackController()` shows the feedback form for the running session. `showFeedbackForm(appToken, takeScreenshot?)` also works without a session. See [Submitting User Feedback](/app-distribution/sdk/user-feedback). +- `getIntegrationInfo()` returns `{ sdkName, crashReportingAvailable: false, coexistenceMode: 'backtrace_crash_owner' }`. +- `setAttribute(key, value)` before `beginWithoutCrashHandler` applies to every session the SDK starts, including sessions started after `stop()`. Limits: 64 attributes, keys up to 64 characters, values up to 1000 characters on iOS and 1024 on Android. +- `setUserId(id)` identifies the tester. `setCorrelationId(...)` and `identify(...)` are deprecated. They write the same user-identity field as `setUserId`. Use `setUserId` plus `setAttribute` instead. +- `addSessionStateListener(listener)` returns a subscription with `remove()`. The listener receives `onSessionStarted({ sessionUrl })`, `onSessionFailed()`, `onSessionLengthReached({ secondsFromStartSession })`, `onSessionStopped()`, and the [app update events](/app-distribution/sdk/app-updates). +- `getSessionUrl()` returns a promise that resolves to the session URL (or `null` before the session is accepted). `getVersion()` returns a promise with the native SDK version. diff --git a/docs/app-distribution/sdk/remote-logging.md b/docs/app-distribution/sdk/remote-logging.md new file mode 100644 index 0000000000..71f9972c52 --- /dev/null +++ b/docs/app-distribution/sdk/remote-logging.md @@ -0,0 +1,133 @@ +--- +id: remote-logging +title: Remote Logging +sidebar_label: Remote Logging +description: Log messages to a Mobile App Distribution session from your app with the Sauce Mobile Beta SDK remote logging methods. +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +

Beta release

+ +Sauce Labs Mobile App Distribution allows developers to log items with a session without logging into the console output. In some cases, workarounds allow you to wrap the Sauce Labs Mobile App Distribution remote logging method in a way that both log to the console and the session. + + + + + +```java +TestFairy.log("", ""); +``` + +Example + +```java +// Be sure to import the SDK +import com.testfairy.TestFairy; + +TestFairy.log("Tag", "Hello, Sauce Labs Mobile App Distribution!"); +``` + + + + + +```objectivec +TFLog(@"", ); +[TestFairy log:@""]; +``` + +Example + +```objectivec +// Be sure to import the SDK +#import "TestFairy.h" + +TFLog(@"Hello, %@", @"Sauce Labs Mobile App Distribution!"); +[TestFairy log:@"Hello, Sauce Labs Mobile App Distribution!"]; +``` + +We recommend sending all calls to NSLog to the SDK so you can continue to use NSLog and see all your log statements in your session.
+To enable this, you have to redefine NSLog using a macro with the following lines. This macro allows you to continue using NSLog in your code while also adding the logs to the matching session in Sauce Labs Mobile App Distribution). + +### Changing Your Prefix Header + +```objectivec +#import "TestFairy.h" +#define NSLog(s, ...) do { NSLog(s, ##__VA_ARGS__); TFLog(s, ##__VA_ARGS__); } while (0) +``` + +1. Add the above line to a global header in your project, accessible to every class file. +2. Update or create a Prefix Header (.pch) for your project. If you do not have a PCH file in your project, you can follow the steps in the next section. + +### Creating a New Prefix Header + +If your project doesn’t already include a Prefix Header (.pch):
+ +1. Create a new file under iOS > Other > PCH File. +2. Name your file `PCH file`. +3. Add these lines of code to the file: + +```objectivec +#import "TestFairy.h" +#define NSLog(s, ...) do { NSLog(s, ##__VA_ARGS__); TFLog(s, ##__VA_ARGS__); } while (0) +``` + +4. From the **Project Navigator**, select your project and the corresponding target. +5. Project > Build Settings > Search: "Prefix Header". +6. Under the compiler settings group, you get the Prefix Header key. +7. Type the file's path, for example: `$(SRCROOT)/$(PROJECT_NAME)/ProjectName-Prefix.pch`. Your file may be at a different location. +8. Make sure the option `Precompile Prefix Header` is set to YES. +9. Clean your project, and rebuild. + +
+ + + +```swift +TestFairy.log("") +``` + +Example + +```swift +// Be sure to import the SDK +import TestFairy + +TestFairy.log("Hello, Sauce Labs Mobile App Distribution!") +``` + +To forward existing `print` or `NSLog` output as well, call `TestFairy.log` from your logging helper. The Objective-C `NSLog` macro approach in the Objective C tab does not apply to Swift `print`. + + + + + +```js +TestFairy.log(""); +``` + +Example + +```js +// Be sure to import the SDK +import TestFairy from '@saucelabs/mobile-beta-react-native'; + +TestFairy.log("Hello, Sauce Labs Mobile App Distribution!"); +``` + +Objects passed to `log` are serialized to JSON before they are sent. + + + +
diff --git a/docs/app-distribution/sdk/security/data-encryption.md b/docs/app-distribution/sdk/security/data-encryption.md new file mode 100644 index 0000000000..86695ff236 --- /dev/null +++ b/docs/app-distribution/sdk/security/data-encryption.md @@ -0,0 +1,74 @@ +--- +id: data-encryption +title: End to End Data Encryption +sidebar_label: Data Encryption +description: Encrypt the logs and screenshots the Sauce Mobile Beta SDK records with your own public/private key pair. +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +

Beta release

+ +End-to-End Data Encryption in Sauce Labs Mobile App Distribution ensures that the logs and screenshots recorded from a mobile device are securely encrypted before being stored on the cloud. This encryption guarantees that only authorized team members with the private key can access and view the recorded data. To use this capability, you will need to create a public key and a private key. The public key initializes the Sauce Mobile Beta SDK in your app and the private key, which should not be shared with anybody, will be used by you when you log in to your Sauce Labs Mobile App Distribution dashboard. + +The encryption process involves using a randomly generated 256-bit AES key (this AES key is only used in a single session recording), which is further protected with an RSA public key. + +## Generating Public/Private Key Pair + +To enable end-to-end data encryption, you need to generate a public/private key pair. The private key should be kept confidential and shared only with authorized team members. Follow the steps below to generate the key pair using the `openssl` tool: + +```bash +openssl genrsa -out testfairy-private-key.pem 2048 +openssl rsa -in testfairy-private-key.pem -outform DER -pubout | base64 - > testfairy-public-key.txt +``` + +It will create two files called `testfairy-private-key.pem` and `testfairy-public-key.txt` containing your private and public keys. + +The file `testfairy-private-key.pem` is sensitive and should not be shared with anyone outside your team. + +The content of `testfairy-public-key.txt` will be used to initialize the SDK. Paste this value into the first parameter of `TestFairy.setPublicKey` method. + +## Android Integration + +Enable end-to-end encryption for your Android apps by calling `setPublicKey` before calling `beginWithoutCrashHandler` (or `begin`): + +```java +TestFairy.setPublicKey(""); +TestFairy.beginWithoutCrashHandler(context, ""); +``` + +## iOS Integration + +Enable end-to-end encryption for your iOS apps by calling `setPublicKey` before calling `beginWithoutCrashHandler` (or `begin`): + +```js +[TestFairy setPublicKey:@""]; +[TestFairy beginWithoutCrashHandler:@""]; +``` + +## Viewing Encrypted Sessions + +Since the data is encrypted using RSA, viewing a session requires the private key. Visiting a recorded session will prompt a dialog for entry of the RSA Private Key. Paste the private key text and click "OK". Your private key is never sent to the server and is only retained in the browser session. + +:::caution +It's crucial that you keep the private key safe. If it is lost, encrypted sessions cannot be recovered, rendering the recorded data useless. +::: + +## Technical Details (How Does It Work?) + +The end-to-end encryption process operates as follows: + +1. As a developer, you generate a private key and derive a public key from it. +2. The public key is integrated into your app, allowing it to encrypt data but not decrypt it. +3. When a new session starts (calling `beginWithoutCrashHandler` after `setPublicKey`), the SDK generates a random 256-bit AES key and encrypts it using the RSA public key you provided. +4. Each session has a unique AES key (in CBC mode) that is not shared between sessions, ensuring strong security. + +:::note +The random key is encrypted by the public key by itself. It means that if a 3rd party wants to view the session, they must run 2^256 brute force combinations to find one session. +::: + +5. AES encryption is employed for data encryption as it is faster than RSA and not limited by the length of the `cleartext` value. +6. The encrypted data includes logs and screenshots, and certain features, such as showing values entered in EditText fields or the text of a button clicked, are automatically disabled when encryption is enabled. + diff --git a/docs/app-distribution/sdk/security/hiding-data.md b/docs/app-distribution/sdk/security/hiding-data.md new file mode 100644 index 0000000000..c3b35b069c --- /dev/null +++ b/docs/app-distribution/sdk/security/hiding-data.md @@ -0,0 +1,130 @@ +--- +id: hiding-data +title: Hiding Sensitive Data +sidebar_label: Hiding Data +description: Hide sensitive views, such as credit card fields, from sessions recorded by the Sauce Mobile Beta SDK. +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +

Beta release

+ +Sauce Labs Mobile App Distribution offers a valuable feature that allows developers to conceal sensitive information from recorded sessions, ensuring that sensitive data, such as credit card information, remains protected during testing and debugging. + +For example, you might want to prevent all information related to credit card data from appearing in the session: + + + + + +To hide a view from video, all you need to do is the following: + +```java +TestFairy.hideView(Integer.valueOf(R.id.my_view)); +``` + +or + +```java +TestFairy.hideView(View myView); +``` + +Replace `R.id.my_view` with the identifier of the view you wish to hide. Review the full example below: + +Example + +```java +public class MyActivity extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + + super.onCreate(savedInstanceState); + setContentView(R.layout.my_activity); + + TestFairy.hideView(findViewById(R.id.credit_card)); + } +} +``` + + + + + +To hide a view from video, all you need to do is call the static instance method hideView in the Sauce Labs Mobile App Distribution class: + +```js +UIView *view = ... +[TestFairy hideView:view]; +``` + +Example + +```js +@interface MyViewController : UIViewController { + IBOutlet UITextField *usernameView; + IBOutlet UITextField *creditCardView; + IBOutlet UITextField *cvvView; +} + +@implementation MyViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + + [TestFairy hideView:creditCardView]; + [TestFairy hideView:cvvView]; +} +``` + + + + + +To hide a view from your recorded session, pass a reference to it to the SDK with `hideView`. Attach a ref to the element and call `hideView` after the component has mounted: + +```jsx +import React, { useEffect, useRef } from 'react'; +import { Text } from 'react-native'; +import TestFairy from '@saucelabs/mobile-beta-react-native'; + +function CardNumber() { + const instructions = useRef(null); + + useEffect(() => { + TestFairy.hideView(instructions.current); + }, []); + + return This will be hidden; +} +``` + +`hideView` accepts the ref's current value, a native view tag (`findNodeHandle(ref.current)`) or a `nativeID` string. + + + + + +### Example + +Below are two screens from a demo video. On the left is the app as it normally looks. On the right, the Card Number field is hidden with `hideView`. + +iphone no hidden HTML elements +iphone hidden HTML elements + +
+ +:::note +- Hidden views are automatically removed from the video before being sent to Sauce Labs Mobile App Distribution's servers, ensuring that sensitive data is never captured or exposed. +- Developers can hide multiple views in a session to protect various sensitive elements in the application. +- It is permissible to add the same view multiple times for hiding without any additional checks. +::: diff --git a/docs/app-distribution/sdk/session-attributes.md b/docs/app-distribution/sdk/session-attributes.md new file mode 100644 index 0000000000..cca10107f3 --- /dev/null +++ b/docs/app-distribution/sdk/session-attributes.md @@ -0,0 +1,137 @@ +--- +id: session-attributes +title: Session Attributes +sidebar_label: Session Attributes +description: Attach key-value attributes to Sauce Mobile Beta SDK sessions to generate better insights and search sessions by your own values. +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +

Beta release

+ +The Sauce Mobile Beta SDK can attach key-value attributes to a session, which helps you generate better insights and lets you search sessions by your own values. + + + + + +```java +TestFairy.setAttribute("", ""); +``` + +Example + +```java +// Be sure to import the SDK +import com.testfairy.TestFairy; + +TestFairy.setAttribute("payment-method","free"); +TestFairy.setAttribute("account-type","driver"); +TestFairy.setAttribute("phone","+1-672-154-5109"); +TestFairy.setAttribute("level","20"); +``` + + + + + +```objectivec +[TestFairy setAttribute:@"" withValue:@""]; +``` + +Example + +```objectivec +// Be sure to import the SDK +#import "TestFairy.h" + +[TestFairy setAttribute:@"name" withValue:@"John Snow"]; +[TestFairy setAttribute:@"phone" withValue:@"+672-14-5109"]; +[TestFairy setAttribute:@"age" withValue:@"20"]; +[TestFairy setAttribute:@"favorite_color" withValue:@"blue"]; +``` + +Swift + +```swift +import TestFairy + +TestFairy.setAttribute("name", withValue: "John Snow") +TestFairy.setAttribute("favorite_color", withValue: "blue") +``` + + + + + +```js +TestFairy.setAttribute("", ""); +``` + +Example + +```js +// Be sure to import the SDK +import TestFairy from '@saucelabs/mobile-beta-react-native'; + +TestFairy.setAttribute("name","John Snow"); +TestFairy.setAttribute("phone","+672-14-5109"); +TestFairy.setAttribute("age","20"); +TestFairy.setAttribute("favorite_color","blue"); +``` + + + + + +The first value is a string `key` to help you search for the attribute in your session. The second parameter, `value`, is any string value for the attribute associated with the session. Neither value can be nil. These attributes are available later in the session recording page, are available via API, and are searchable. + +Adding these lines will mark this session with the values above, so when you review the recording, you have more information about the person running the app. + +:::note + +- `setAttribute` may be called many times. +- You may call `setAttribute` before or after `beginWithoutCrashHandler` (or `begin`). Attributes set before the session starts are kept by the SDK and applied to every session it starts, including the new session created after `stop()` and resume. +- Limits: 64 attributes per session, keys of up to 64 characters, and values of up to 1000 characters on iOS and 1024 characters on Android. On Android and iOS, `setAttribute` returns a boolean and returns `false` when the attribute is rejected, for example when the limit is exceeded. + +::: + +## Reserved Attributes for Backtrace Coexistence + +When you run the SDK together with Backtrace (Sauce Labs Error Reporting), a small set of attributes is shared by both SDKs so that a crash report in Backtrace and the session recording in Sauce Labs Mobile App Distribution can be joined. Treat these keys as reserved: set them with `setAttribute` before `beginWithoutCrashHandler`, send the same values to Backtrace, and do not reuse the keys for anything else. + +| Attribute | Meaning | +|---|---| +| `sauce.correlation_id` | One lowercase UUID v4 generated per app launch before either SDK starts. It is the join key between a Backtrace report and its session. | +| `sauce.sdk.coexistence_mode` | Always `backtrace_crash_owner`: Backtrace owns crash reporting and the SDK does not install a crash handler. | +| `sauce.environment` | Deployment environment of the build, for example `beta` or `production`. | +| `sauce.release` | Release identifier in the form `@` (application or bundle ID and marketing version). | +| `sauce.dist` | Build number (`versionCode` on Android, `CFBundleVersion` on iOS). | +| `mad.distribution_id` | The Mobile App Distribution distribution ID. Set it only when your build is distributed through it. | + +```java +// Android example; the keys and values are identical on iOS and React Native. +String correlationId = UUID.randomUUID().toString(); // generated before either SDK starts + +TestFairy.setAttribute("sauce.correlation_id", correlationId); +TestFairy.setAttribute("sauce.sdk.coexistence_mode", "backtrace_crash_owner"); +TestFairy.setAttribute("sauce.environment", "beta"); +TestFairy.setAttribute("sauce.release", BuildConfig.APPLICATION_ID + "@" + BuildConfig.VERSION_NAME); +TestFairy.setAttribute("sauce.dist", String.valueOf(BuildConfig.VERSION_CODE)); +TestFairy.beginWithoutCrashHandler(getApplicationContext(), ""); +``` + +The reverse link is written on the Backtrace side only: on every session start, the app copies the session URL into the Backtrace attributes `sauce.mobile_beta.session_url` and `sauce.mobile_beta.session_started` (`"true"` or `"false"`) from a session state listener (`TestFairy.addSessionStateListener` on Android and React Native, `TestFairy.setSessionStateDelegate` on iOS). A launch can produce several sessions (`stop()` followed by resume), so overwrite these values on every start. Never set them once. + +Do not use the deprecated `setCorrelationId` or `identify` for `sauce.correlation_id`: they write the user-identity field, not an attribute. Keep `setUserId` for the real user. See [Identifying Your Users](/app-distribution/sdk/identifying-users). + +In Backtrace, a custom attribute becomes filterable once it is indexed under Project Settings, Attributes (see [Backtrace attributes](/error-reporting/project-setup/attributes/)). Index `sauce.correlation_id` with the UUID format. In the Sauce Labs Mobile App Distribution dashboard, search the session list for the same value. The initialization order and complete examples for each platform are in [Using Sauce Mobile Beta with Backtrace](/app-distribution/sdk/backtrace-coexistence). diff --git a/docs/app-distribution/sdk/supported-platforms.md b/docs/app-distribution/sdk/supported-platforms.md new file mode 100644 index 0000000000..febdc7d0ba --- /dev/null +++ b/docs/app-distribution/sdk/supported-platforms.md @@ -0,0 +1,54 @@ +--- +id: supported-platforms +title: Supported Platforms +sidebar_label: Supported Platforms +description: The Sauce Mobile Beta SDK supports Android, iOS, React Native, and Expo with prebuild. +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +

Beta release

+ +The Sauce Mobile Beta SDK is available for Android, iOS and React Native, including Expo projects that generate their native projects with prebuild. The SDK does not install a crash handler. Crash reporting is provided by Backtrace (Sauce Labs Error Reporting), and the two SDKs are designed to run in the same app. See [Using Sauce Mobile Beta with Backtrace](/app-distribution/sdk/backtrace-coexistence). + +The runtime API is the same on every platform. Import the `TestFairy` module on iOS, `com.testfairy.TestFairy` on Android, and the `TestFairy` default export in React Native. The feature pages below show each platform's syntax side by side. + +## Android + +- [Adding the SDK to Your App](/app-distribution/sdk/android/integrating-android) +- [Using Sauce Mobile Beta with Backtrace](/app-distribution/sdk/backtrace-coexistence) +- [Begin with Options](/app-distribution/sdk/options) +- [Identifying Your Users](/app-distribution/sdk/identifying-users) +- [Session Attributes](/app-distribution/sdk/session-attributes) +- [Submitting User Feedback](/app-distribution/sdk/user-feedback) +- [App Updates](/app-distribution/sdk/app-updates) +- [Logging](/app-distribution/sdk/logging) and [Remote Logging](/app-distribution/sdk/remote-logging) +- [Hiding Sensitive Data](/app-distribution/sdk/security/hiding-data) +- [Using Map Locations](/app-distribution/sdk/map-locations) + +## iOS + +- [Adding the SDK to Your App](/app-distribution/sdk/ios/integrating-ios) +- [Using Sauce Mobile Beta with Backtrace](/app-distribution/sdk/backtrace-coexistence) +- [Begin with Options](/app-distribution/sdk/options) +- [Identifying Your Users](/app-distribution/sdk/identifying-users) +- [Session Attributes](/app-distribution/sdk/session-attributes) +- [Submitting User Feedback](/app-distribution/sdk/user-feedback) +- [App Updates](/app-distribution/sdk/app-updates) +- [Logging](/app-distribution/sdk/logging) and [Remote Logging](/app-distribution/sdk/remote-logging) +- [Hiding Sensitive Data](/app-distribution/sdk/security/hiding-data) and [Hiding Webview Elements](/app-distribution/sdk/ios/hiding-webview) +- [Log Network](/app-distribution/sdk/ios/log-network) +- [Using Map Locations](/app-distribution/sdk/map-locations) + +## React Native + +- [Adding the SDK to Your App](/app-distribution/sdk/react-native/react-native), including [Expo](/app-distribution/sdk/react-native/expo) (with prebuild) +- [Using Sauce Mobile Beta with Backtrace](/app-distribution/sdk/backtrace-coexistence) +- [Identifying Your Users](/app-distribution/sdk/identifying-users) +- [Session Attributes](/app-distribution/sdk/session-attributes) +- [Submitting User Feedback](/app-distribution/sdk/user-feedback) +- [App Updates](/app-distribution/sdk/app-updates) +- [Logging](/app-distribution/sdk/logging) and [Remote Logging](/app-distribution/sdk/remote-logging) +- [Hiding Sensitive Data](/app-distribution/sdk/security/hiding-data) diff --git a/docs/app-distribution/sdk/tf-production.md b/docs/app-distribution/sdk/tf-production.md new file mode 100644 index 0000000000..f55d90e252 --- /dev/null +++ b/docs/app-distribution/sdk/tf-production.md @@ -0,0 +1,298 @@ +--- +id: tf-production +title: Sauce Mobile Beta SDK in Production +sidebar_label: Sauce Mobile Beta SDK in Production +description: Keep the Sauce Mobile Beta SDK out of store builds, or follow these guidelines if you run it in a production app. +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +

Beta release

+ +The Sauce Mobile Beta SDK is built for beta and tester builds. The recommended layout keeps it out of store builds entirely and ships only Backtrace (Sauce Labs Error Reporting) to production. See [Keep the SDK Out of Store Builds](#recommended-keep-the-sdk-out-of-store-builds). If you do run the SDK in a production app, for example for customer support sessions, follow the guidelines below. + +The SDK does not install a crash handler. Production crash reporting comes from [Backtrace](/error-reporting/getting-started/), which ships in your production builds whether or not the SDK does. See [Using Sauce Mobile Beta with Backtrace](/app-distribution/sdk/backtrace-coexistence). + +## Running the SDK in Production + +When running the SDK in production, you may record sensitive data such as medical information, financial data or photos. + +Therefore it is important to follow these guidelines: + +1. On iOS, you **must** request explicit user consent before recording start. + See the [Apple guidelines](https://developer.apple.com/app-store/review/guidelines/) and pay special attention to section 2.5.14 + +1. When recording sensitive data you **must** use the SDK's [end-to-end encryption](/app-distribution/sdk/security/data-encryption) with your own private keys, so that only your team can see your sessions. + +1. You **must** [hide sensitive data](/app-distribution/sdk/security/hiding-data) such as credit card numbers, passwords, or other PII, so that this info will not be uploaded to the server. + +1. In case you are using the SDK for customer support to better understand your users in case of a technical issue, + it is recommended to add a button to your app menu (call it "advanced support"?) and have that button call `TestFairy.beginWithoutCrashHandler()`. + Before starting the session, ask the user if it is ok to record their screen for quality assurance purposes. + When doing that, make sure that the session duration is set to 2-3 minutes, which is long enough to identify the cause of a problem. + +1. You **must** include a proper disclaimer in your app terms of service document. + You must explain exactly what data you collect, and how to request deletion of that data. + +1. Never use auto update with apps that are shipped to production. Letting a store app update itself violates the [App Store Review Guidelines](https://developer.apple.com/app-store/review/guidelines/) and the [Play Store Developer Distribution Agreement](https://play.google.com/about/developer-distribution-agreement.html). Call `disableAutoUpdate` before `beginWithoutCrashHandler` in such builds. See [App Updates](/app-distribution/sdk/app-updates). + +## Recommended: Keep the SDK Out of Store Builds + +The most reliable way to keep the SDK out of production is to not compile it into store builds at all. Disabling a feature at runtime does not change what code is packaged. A dependency-level switch does. Backtrace is a production tool and is declared as a regular dependency in every variant, so store builds keep crash reporting while dropping session recording, tester feedback and update prompts. Both Sauce Labs demo apps use this layout. + +### Android: Debug-Only Dependency + +Declare the SDK for the debug variant (or a `beta` product flavor) only, and keep Backtrace under `implementation`: + +```groovy +dependencies { + // Crash reporting ships in every variant. + implementation "com.github.backtrace-labs.backtrace-android:backtrace-library:" + + // Sauce Mobile Beta ships in debug builds only. With a beta flavor, use betaImplementation. + debugImplementation "com.saucelabs.mobilebeta:sauce-mobile-beta-android:2.2.0-rc" +} +``` + +Use Backtrace Android SDK 3.7.14 or later so that `BacktraceClient.addAttribute` is available. + +Because the `com.testfairy` classes do not exist in the release variant, route all SDK calls through one class that has two implementations in variant-specific source sets. The example below is simplified from the [My Demo App for Android](https://github.com/saucelabs/my-demo-app-android) source set, where `app/src/mobileBeta/java` holds the real integration and `app/src/noMobileBeta/java` holds empty methods: + +```groovy +android { + sourceSets { + debug.java.srcDir 'src/mobileBeta/java' + release.java.srcDir 'src/noMobileBeta/java' + } +} +``` + +```java +// src/mobileBeta/java/com/example/SauceMobileBetaIntegration.java (debug builds) +final class SauceMobileBetaIntegration { + static void initialize(Application application, Map sharedAttributes) { + TestFairy.disableAutoUpdate(); + for (Map.Entry attribute : sharedAttributes.entrySet()) { + TestFairy.setAttribute(attribute.getKey(), attribute.getValue()); + } + TestFairy.beginWithoutCrashHandler(application.getApplicationContext(), ""); + } +} + +// src/noMobileBeta/java/com/example/SauceMobileBetaIntegration.java (release builds) +final class SauceMobileBetaIntegration { + static void initialize(Application application, Map sharedAttributes) { + // No SDK in release builds. + } +} +``` + +Release builds then contain neither the SDK nor any call to it, and a release build that accidentally references `TestFairy` fails to compile instead of shipping the SDK. + +### iOS: Info.plist and xcconfig Switch + +A Swift package product is linked into every build configuration, so the switch is a runtime one driven by build settings rather than code changes. [My Demo App for iOS](https://github.com/saucelabs/my-demo-app-ios) reads two Info.plist keys: a boolean `testfairyEnabled` and the token `sauceMobileBetaToken`, which Xcode expands from a build setting defined in an `.xcconfig` file. The SDK runs only when the flag is true and a token is present. Otherwise the app uses a no-op wrapper (see Option 3 below). + +```text +// Config/Demo.xcconfig (committed, empty defaults; the app target's base configuration) +SAUCE_MOBILE_BETA_TOKEN = +#include? "Local.xcconfig" + +// Config/Local.xcconfig (gitignored; written from CI secrets for beta builds) +SAUCE_MOBILE_BETA_TOKEN = +``` + +```xml + +sauceMobileBetaToken +$(SAUCE_MOBILE_BETA_TOKEN) +testfairyEnabled + +``` + +```swift +let info = Bundle.main.infoDictionary ?? [:] +let enabled = info["testfairyEnabled"] as? Bool ?? false +let token = info["sauceMobileBetaToken"] as? String ?? "" + +if enabled && !token.isEmpty { + TestFairy.beginWithoutCrashHandler(token) +} +``` + +For the App Store build, the release pipeline flips the flag and clears the token before building, so the shipped bundle carries no token and never starts a session: + +```bash +/usr/libexec/PlistBuddy -c "SET testfairyEnabled NO" "My App/Info.plist" +sed -i '' 's/^SAUCE_MOBILE_BETA_TOKEN = .*/SAUCE_MOBILE_BETA_TOKEN =/' Config/Local.xcconfig +``` + +Backtrace is initialized unconditionally before this check, so production crashes are reported even when the SDK does not start. If you also want the SDK's code out of the App Store binary, combine this switch with Option 2 below. + +## Disabling the SDK in Production + +When it comes to using the SDK in a production environment, there may be instances where you need to disable it temporarily or permanently, for example to comply with platform guidelines or to eliminate potential performance impacts. The SDK is modular, and the options below keep working alongside the dependency-level approach above. + +### iOS + +#### Option 1: Calling `beginWithoutCrashHandler` Only in DEBUG + +Without a call to `beginWithoutCrashHandler` (or `begin`), the SDK is not initialized. An uninitialized SDK won't consume any memory and won't open sockets. Even though it does not impact your app in any way, the SDK is still linked with your app. + + + + + +```objectivec +#ifdef DEBUG +[TestFairy beginWithoutCrashHandler:@""]; +#endif +``` + + + + + +```swift +#if DEBUG +TestFairy.beginWithoutCrashHandler("") +#endif +``` + + + + +If your publishing workflow has multiple build schemes, define a compiler flag for each scheme and enable the SDK only for the schemes relevant to testing: + + + + + +```objectivec +#if defined(DEBUG) +[TestFairy beginWithoutCrashHandler:@""]; +#elif defined(SCHEME1) +[TestFairy beginWithoutCrashHandler:@""]; +#elif defined(SCHEME2) +[TestFairy beginWithoutCrashHandler:@""]; +#else +// Don't initialize the SDK +#endif +``` + + + + + +```swift +#if DEBUG +TestFairy.beginWithoutCrashHandler("") +#elseif SCHEME1 +TestFairy.beginWithoutCrashHandler("") +#elseif SCHEME2 +TestFairy.beginWithoutCrashHandler("") +#else +// Don't initialize the SDK +#endif +``` + + + + +If you are also worried about reducing the app size in your final release build, proceed to Option 2. + +#### Option 2: Exclude the SDK From the App Store Scheme + +A common pattern is a dedicated scheme for the App Store, in addition to Debug and Release. This option still requires the `#ifdef` or `#if` directives from Option 1, but also omits the library from the App Store binary. + +With the Swift package (`SauceMobileBeta`), a package product is linked per target, not per configuration. Create a separate app target for the App Store that does not depend on the `SauceMobileBeta` product, and keep the runtime switch above for the targets that do. + +Try building your project. If the compilation fails, locate the lines where the SDK is used and wrap them with the `#ifdef` or `#if` directives explained in Option 1. + +#### Option 3: No-Op Wrapper + +Similar to Option 2, you keep multiple schemes or targets, but you do not need `#ifdef` or `#if` around every call. Route every SDK call through a wrapper protocol with a real implementation and a no-op implementation, and select the implementation from the `testfairyEnabled` flag described in [iOS: Info.plist and xcconfig Switch](#ios-infoplist-and-xcconfig-switch) or from a compiler flag. All `TestFairy` calls live in one file, and the rest of the app never imports the SDK. `TestFairyWrapper.swift` in [My Demo App for iOS](https://github.com/saucelabs/my-demo-app-ios) is a complete example. + +```swift +protocol SauceMobileBetaProtocol { + func begin() + func showFeedbackForm() +} + +final class SauceMobileBetaLive: SauceMobileBetaProtocol { + func begin() { TestFairy.beginWithoutCrashHandler("") } + func showFeedbackForm() { TestFairy.showFeedbackForm() } +} + +final class SauceMobileBetaNoOp: SauceMobileBetaProtocol { + func begin() {} + func showFeedbackForm() {} +} +``` + +### Android + +#### Option 1: Calling `beginWithoutCrashHandler` Only in Debug Builds + +Your Gradle variants can alter the code path of your app. Use the debug variant to call `TestFairy.beginWithoutCrashHandler()`, and the release variant to omit the call. + +In order for ProGuard or R8 to fully remove the SDK from the final binary, use a wrapper class that differs in each of your variants: + +- Create the same package structure and class name in `src/debug/java` and in `src/release/java` (not in `src/main/java`, or the two definitions clash). +- Put the code below into the class under `src/debug/java`. + +```java +public class TestFairyWrapper { + public static void begin(Context context, String appToken) { + TestFairy.beginWithoutCrashHandler(context, appToken); + } +} +``` + +- Put the code below into the class under `src/release/java`. + +```java +public class TestFairyWrapper { + public static void begin(Context context, String appToken) { + // Do nothing + } +} +``` + +- Call `TestFairyWrapper.begin()` from your `Application` subclass. + +Without any calls to the SDK, ProGuard or R8 removes the entire compiled code from the resulting `classes.dex` and the final APK. Combined with a `debugImplementation` dependency (recommended above), the release APK never contains it in the first place. + +#### Option 2: Use a Class Loader + +Android allows advanced developers to load classes into memory on the fly. You can use Java reflection to load the SDK class into memory only on a debug build. + +Replace the code where you call `TestFairy.beginWithoutCrashHandler()` with the code below. + +```java +try { + Class cls = Class.forName("com.testfairy.TestFairy"); + Method method = cls.getMethod("beginWithoutCrashHandler", android.content.Context.class, String.class); + method.invoke(null, this, ""); +} catch (Exception e) { /* ignore */ } +``` + +Then, in your `build.gradle` file, declare the SDK with `debugImplementation`: + +```groovy +debugImplementation "com.saucelabs.mobilebeta:sauce-mobile-beta-android:2.2.0-rc" +``` diff --git a/docs/app-distribution/sdk/user-feedback.md b/docs/app-distribution/sdk/user-feedback.md new file mode 100644 index 0000000000..8c97115b6b --- /dev/null +++ b/docs/app-distribution/sdk/user-feedback.md @@ -0,0 +1,315 @@ +--- +id: user-feedback +title: Submitting User Feedback +sidebar_label: Submitting User Feedback +description: Collect bug reports and suggestions from testers in your app with the Sauce Mobile Beta SDK In-App Feedback feature. +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +

Beta release

+ +Getting feedback from users and testers is crucial in the app development process. It provides valuable insights and helps improve the overall user experience. Sauce Labs Mobile App Distribution offers an effortless way to collect feedback through its In-App Feedback feature. By integrating the Sauce Mobile Beta SDK into your app, you can enable users to report bugs, suggest improvements, and share their thoughts directly in the app. + +## Using In-App Feedback + +Sauce Labs Mobile App Distribution provides an effortless way to collect this feedback. If you [added the Sauce Mobile Beta SDK](/app-distribution/sdk/adding-tf-sdk) to your app, then all you need to do is enable the **In-App Bug Reporting** feature in your build settings in the Sauce Labs Mobile App Distribution dashboard, and you can start collecting feedback from your users with "shake to report": + +Enable Shake to Feedback + +Users or testers can initiate the feedback collection process by shaking their devices while using the app. When they shake the device, the feedback form will be triggered, allowing them to report bugs or share their suggestions. + +This feedback will be added to the app session they are running. + +All feedback includes a screenshot, device information, submitter email, and text comments added. The feedback is added to the event timeline so you can find it without difficulty. + +## Feedback Contents + +When users provide feedback using the In-App Bug Reporting feature, the following information will be included: + +- **Screenshot** - A screenshot of the app at the moment the feedback was triggered. +- **Device Information** - Details about the device, such as model, OS version, and other relevant technical information. +- **Submitter Email** - If available, the email address of the user or tester providing the feedback. +- **Text Comments** - Users can include specific comments to describe the issue or suggestion they are reporting. +- **Event Timeline** - The feedback will be added to the app's event timeline, so developers can track and analyze the feedback. + +## Customizing In-App Feedback + +Sauce Labs Mobile App Distribution allows you to customize the way In-App Feedback is collected. If you prefer not to use the shake gesture for feedback collection, you can programmatically invoke the feedback form using a button click or any other gesture in your app. This way, users can access the feedback form from a designated area in the app, like the help menu or after encountering unexpected errors. + +If you choose to invoke the feedback form programmatically, it will be shown regardless if the in-app feedback is disabled in your build settings. + + + + + +```java +TestFairy.showFeedbackForm(); +``` + +Example + +```java +// Be sure to import the SDK +import com.testfairy.TestFairy; + +// Can be invoked on a button press +// or after your app passes a given page +TestFairy.showFeedbackForm(); +``` + +:::note + +`showFeedbackForm()` requires a running session. To collect feedback without a session, call `TestFairy.showFeedbackForm(context, "", true)`. For custom fields, validation and callbacks, see [Customizing the Feedback Form](#customizing-the-feedback-form). + +::: + + + + + +```objectivec +[TestFairy showFeedbackForm]; +``` + +Example + +```objectivec +// Be sure to import the SDK +#import "TestFairy.h" + +// Can be invoked on a button press +// or after your app passes a given page +[TestFairy showFeedbackForm]; +``` + +Swift + +```swift +import TestFairy + +TestFairy.showFeedbackForm() +``` + +:::note + +On iOS, if the In-App Bug Reporting feature is enabled, the feedback form will also be shown when the tester takes a screenshot. + +`pushFeedbackController` is deprecated. Use `showFeedbackForm`, which requires a running session. To collect feedback without one, call `[TestFairy showFeedbackForm:@"" takeScreenshot:YES]`. To hide the email field or add custom fields, see [Customizing the Feedback Form](#customizing-the-feedback-form) (`setFeedbackEmailVisible:` is deprecated). + +::: + + + + + +```js +TestFairy.pushFeedbackController(); +``` + +Example + +```js +// Be sure to import the SDK +import TestFairy from '@saucelabs/mobile-beta-react-native'; + +// Can be invoked on a button press +// or after your app passes a given page +TestFairy.pushFeedbackController(); + +// Without a running session: +TestFairy.showFeedbackForm('', true); +``` + +On React Native, `pushFeedbackController()` shows the form for the running session. `showFeedbackForm(appToken, takeScreenshot)` also works without a session. + + + + + +## Customizing the Feedback Form + +The built-in form has an email field, a message field and, depending on the platform, buttons to attach a screenshot or a screen recording. You customize it by building a feedback options object and handing it to the SDK before the form is shown. Doing so before `beginWithoutCrashHandler` is fine. The feedback classes are in the `TestFairy` module on iOS and the `com.testfairy` package on Android. + +The form can be shown in two ways. `showFeedbackForm()` with no arguments requires a running session and attaches the feedback to it. The overload that takes the app token (`showFeedbackForm(context, appToken, takeScreenshot)` on Android, `showFeedbackForm(appToken, takeScreenshot:)` on iOS and `showFeedbackForm(appToken, takeScreenshot)` on React Native) works without a session, optionally captures a screenshot first, and the feedback appears in the build's Feedbacks tab. + +Rules that apply on Android and iOS: + +- Setting a list of form fields **replaces** the default fields. To keep the email and message fields, add them yourself with the reserved attribute names `:userId` (email or user identifier) and `:text` (message). Always include a `:text` field. +- Every other field is sent with the feedback as a feedback attribute named after the field's attribute key, so pick short, stable names. +- Three field types are built in: single-line text (String), multi-line text (TextArea) and a dropdown list (Select, a map of label to value). You can add up to 32 fields. Attribute names must be non-empty and unique (duplicates are dropped). For any other control, implement the `FeedbackFormField` interface (`onCreateView`, `getAttribute`, `getValue`) yourself. +- The reserved fields are pre-filled by the SDK (last used email, unsent draft). The `defaultText` option only applies to the built-in form and is ignored after you set custom fields. +- Email is mandatory by default. Hiding the email field lifts the requirement, and so does a custom field list without a `:userId` field. +- A custom verifier **replaces** the SDK's default validation (email present and valid when mandatory, non-empty message), so re-implement the checks you still want. + + + + + +```java +import com.testfairy.FeedbackContent; +import com.testfairy.FeedbackFormField; +import com.testfairy.FeedbackOptions; +import com.testfairy.FeedbackVerifier; +import com.testfairy.SelectFeedbackFormField; +import com.testfairy.StringFeedbackFormField; +import com.testfairy.TestFairy; +import com.testfairy.TextAreaFeedbackFormField; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +Map types = new LinkedHashMap<>(); // label -> value +types.put("Bug", "bug"); +types.put("Feature request", "feature"); + +List fields = new ArrayList<>(); +// Reserved names keep the built-in fields (attribute, placeholder, default value): +fields.add(new StringFeedbackFormField(":userId", "Your email", "")); +fields.add(new TextAreaFeedbackFormField(":text", "Describe the problem", "")); +// Your own fields (any attribute name): +fields.add(new StringFeedbackFormField("phone", "Phone number", "")); +fields.add(new SelectFeedbackFormField("type", "Is this a bug or a feature request?", types, "Bug")); // the default is a label + +FeedbackOptions options = new FeedbackOptions.Builder() + .setFeedbackFormFields(fields) + .setEmailMandatory(false) // default true; only enforced when a :userId field exists + .setTakeScreenshotButtonVisible(true) + .setRecordVideoButtonVisible(false) + .setFeedbackInterceptor(content -> content) // optional: inspect or modify before sending; never return null + .setCallback(new FeedbackOptions.Callback() { // optional + @Override public void onFeedbackSent(FeedbackContent content) {} + @Override public void onFeedbackCancelled() {} + @Override public void onFeedbackFailed(int reason, FeedbackContent content) {} + }) + .build(); +TestFairy.setFeedbackOptions(options); + +// Optional: custom validation (returning false blocks sending and shows your message) +TestFairy.setFeedbackVerifier(new FeedbackVerifier() { + @Override public boolean verifyFeedback(FeedbackContent content) { + return content.getEmail().endsWith("@yourcompany.com") && !content.getText().isEmpty(); + } + @Override public String getVerificationFailedMessage() { + return "Please use your company email and describe the issue."; + } +}); + +// Show the form from a button after beginWithoutCrashHandler ... +TestFairy.showFeedbackForm(); +// ... or without a running session, optionally taking a screenshot first: +TestFairy.showFeedbackForm(context, "", true); +``` + +Other `FeedbackOptions.Builder` options: + +- `setEmailFieldVisible(boolean)` hides the email field (and lifts the mandatory requirement). +- `setDefaultText(String)` pre-fills the message of the built-in form. +- `setBrowserUrl(String)` opens your own web form in the browser instead of the native form. The SDK appends `sessionUrl`, `timestamp`, `user` (your `setUserId` value), `platform`, `packageName`, `versionName`, `versionCode` and `screenName` as query parameters. The other options do not apply in this mode. + +Shake trigger: call `TestFairy.enableFeedbackForm("shake")` (Android accepts only `"shake"`) or `TestFairy.disableFeedbackForm()` before `beginWithoutCrashHandler`. + + + + + +```swift +import TestFairy // the TestFairy module of the SauceMobileBeta package + +let options = TestFairyFeedbackOptions.create { builder in + builder?.title = "Send us feedback" + builder?.isEmailVisible = true + builder?.isEmailMandatory = false + + builder?.feedbackFormFields = [ + // Reserved names keep the built-in fields: + TestFairyStringFeedbackFormField(attribute: ":userId", label: nil, + placeholder: "Your email", defaultValue: "")!, + TestFairyTextAreaFeedbackFormField(attribute: ":text", + placeholder: "Describe the problem", defaultValue: "")!, + // Your own fields (any attribute name): + TestFairyStringFeedbackFormField(attribute: "phone", label: nil, + placeholder: "Phone number", defaultValue: "")!, + TestFairySelectFeedbackFormField(attribute: "type", label: "Is this a bug or a feature request?", + values: ["Bug": "bug", "Feature": "feature"], + defaultValue: "Bug")!, // defaultValue is one of the labels + ] + + // Optional: validate before sending (false blocks sending and shows your message) + builder?.verifier = CompanyEmailVerifier() + // Optional: inspect or modify the content right before it is sent (never return nil) + builder?.interceptor = { content in content } +} +TestFairy.setTestFairyFeedbackOptions(options) + +// Show the form from a button after beginWithoutCrashHandler ... +TestFairy.showFeedbackForm() +// ... or without a running session, optionally attaching a screenshot: +TestFairy.showFeedbackForm("", takeScreenshot: true) +``` + +```swift +final class CompanyEmailVerifier: NSObject, TestFairyFeedbackVerifier { + func verifyFeedback(_ content: TestFairyFeedbackContent!) -> Bool { + let email = content.email ?? "" + let text = content.text ?? "" + return email.hasSuffix("@yourcompany.com") && !text.isEmpty + } + + func getVerificationFailedMessage() -> String! { + return "Please use your company email and describe the issue." + } +} +``` + +Objective-C uses the same types: `[TestFairyFeedbackOptions createWithBlock:^(TestFairyFeedbackOptionsBuilder *builder) { ... }]` followed by `[TestFairy setTestFairyFeedbackOptions:options]`. The `defaultText` builder property pre-fills the message of the built-in form. The dictionary-based `setFeedbackOptions:` and `setFeedbackEmailVisible:` are deprecated. The dropdown list shows its options in dictionary order, which is not necessarily the order you typed them. + +Triggers: call `TestFairy.enableFeedbackForm("shake")`, `"screenshot"` or `"shake|screenshot"`, or `TestFairy.disableFeedbackForm()`, before `beginWithoutCrashHandler`. + + + + + +The bridge exposes the basic options only, not custom fields: + +```js +import TestFairy from '@saucelabs/mobile-beta-react-native'; + +TestFairy.setFeedbackOptions({ + defaultText: '', + isEmailMandatory: false, + isEmailVisible: true, + isTakeScreenshotButtonVisible: true, // Android only; ignored on iOS + isTakeRecordingButtonVisible: false, // Android only; ignored on iOS + // browserUrl: 'https://...', // Android only: open your own web form instead +}); +TestFairy.enableFeedbackForm('shake'); // before begin; iOS also accepts 'screenshot' and 'shake|screenshot' + +// With a running session: +TestFairy.pushFeedbackController(); +// Without a running session (appToken, takeScreenshot); takeScreenshot defaults to true: +TestFairy.showFeedbackForm('', true); +``` + +Custom fields on React Native need a small native module that builds the options exactly as shown in the Android and iOS tabs. On Android, do not call `TestFairy.setFeedbackOptions()` from JavaScript afterwards: it rebuilds the options from scratch and replaces the ones your native module set. + + + + diff --git a/docs/mobile-apps/ms-app-center.md b/docs/mobile-apps/ms-app-center.md index 5541bf5e42..3509f27c8b 100644 --- a/docs/mobile-apps/ms-app-center.md +++ b/docs/mobile-apps/ms-app-center.md @@ -4,28 +4,30 @@ title: Microsoft App Center Alternative sidebar_label: MS App Center Alternative --- -Migrate now from App Center App Distribution to Sauce Labs! This is how you can get started with our [Sauce Labs Mobile App Distribution and Beta Testing solution](/testfairy). +Migrate now from App Center App Distribution to Sauce Labs. Get started with our [Sauce Labs Mobile App Distribution and Beta Testing solution](/testfairy). ## What You'll Need - A Sauce Labs account -- [Mobile Beta Testing access (https://mobile.saucelabs.com/) +- [Mobile Beta Testing access](https://mobile.saucelabs.com/) -## Start migrating now from App Center! Upload your apps to Sauce Labs Mobile App Distribution! +## Start Migrating Now From App Center: Upload Your Apps to Sauce Labs Mobile App Distribution Once your account is created and verified, you can upload your app. Click New Upload to upload an app. -We recommend using the Upload API method to enable the Jenkins plugin, Gradle plugin, or Command line uploader like Fastlane. +We recommend using the Upload API method to enable the Jenkins plugin, Gradle plugin, or Command line uploader like Fastlane. ## Distribute App to Testers -Sauce Labs Mobile App Distribution offers enterprise-grade app distribution capabilities, allowing companies to easily and securely distribute the right apps to the right users. The platform allows admins to enforce corporate security policies during testing, and has the capability to automatically update apps to new versions or revoke access to installed apps or users. +Sauce Labs Mobile App Distribution offers enterprise-grade app distribution capabilities, allowing companies to securely distribute the right apps to the right users. The platform allows admins to enforce corporate security policies during testing, and has the capability to automatically update apps to new versions or revoke access to installed apps or users. For more information, see [Managing Testers](/testfairy/testers/managing-testers/). ## Security -Sauce Labs Mobile App Distribution is available as a private cloud or an on-premise installation and can integrate with any SAML single sign-on service. +Sauce Labs Mobile App Distribution is available as a private cloud or an on-premise installation and can integrate with any SAML single sign-on service. Sauce Labs Mobile App Distribution is the only platform that provides end-to-end data encryption using your private/public keys, so your data remains private. + +For more information, see [End to End Data Encryption](/app-distribution/sdk/security/data-encryption). :::tip Use the [Sauce Labs Mobile App Distribution API](/testfairy/api-reference/upload-api/) to upload or retrieve your mobile applications. diff --git a/docs/testfairy/api-reference/rest-api.md b/docs/testfairy/api-reference/rest-api.md index 9ca032edd2..f78f56bf29 100644 --- a/docs/testfairy/api-reference/rest-api.md +++ b/docs/testfairy/api-reference/rest-api.md @@ -83,6 +83,12 @@ curl -u "john@example.com:00001234cafecafe" "https://mobile.saucelabs.com/api/1/ curl -u "john@example.com:coffee00001234" "https://mobile.eu-central-1.saucelabs.com/api/1/projects/" ``` +## Previous Sauce Labs Mobile App Distribution US-East endpoint: + +```bash +curl -u "john@example.com:00001234cafecafe" "https://api.testfairy.com/api/1/projects/" +``` + A project is either an iOS app or an Android app (two apps with the same package name but on different platforms are considered two projects.) In the example above, the user is `john@example.com` and the API key is `0001234cafecafe`. This authentication is required for all requests to the REST server. @@ -519,7 +525,7 @@ Update editable fields of a specific build. Only the fields included in the requ metadata_* -

| OPTIONAL | STRING |

Custom metadata fields. Use the prefix metadata_ followed by a key name (e.g. metadata_title, metadata_env).

+

| OPTIONAL | STRING |

Custom metadata fields. Use the prefix metadata_ followed by a key name (for example, metadata_title, metadata_env).

@@ -729,6 +735,105 @@ Invite one or more tester groups to this specific build. You can optionally send +--- + +### List All Recorded Sessions in Build + +
+ GET/api/1/projects/{project-id}/builds/{build-id}/sessions/ +

+ +Get metadata for all sessions recorded for a specific build. + +#### Responses + + + + + + + + +
200Success.
+ +```json title="Sample Response" +{ + "status": "ok", + "sessions": [ + { + "id": 1, + "self": "/projects/2197059-demoapp/builds/4867553/sessions/1", + "startTime": "2017-01-22 16:42:40", + "duration": "15:01", + "testerEmail": "john@testfairy.com", + "device": "Samsung - Samsung Galaxy S8", + "ipAddress": "23.100.122.175", + "crashed": false, + "countryName": "United States", + "countryCode": "us" + } + ] +} +``` + +
+ +--- + +### Get Session Data, Events and Logs + +
+ GET/api/1/projects/{project-id}/builds/{build-id}/sessions/{session-id} +

+ +Get metadata (and optionally data) for a specific session. + +#### Parameters + + + + + + + + +
fields

| OPTIONAL | STRING |

Possible values: meta, logs, events. The default value is meta. Use events to load all events, screenshots, touches, and other metrics. Use logs to fetch only logs. When loading logs, the response is application/text.

+ +#### Responses + + + + + + + + +
200Success.
+ +```json title="Sample Response" +{ + "status": "ok", + "session": { + "id":4426273741, + "sessionStartTime":"2019-05-20 09:05:30", + "duration":"00:27", + "testerEmail":"blabla@ex.com", + "device":"Xiaomi - Redmi S2", + "ipAddress":"84.94.200.136", + "crashed":false, + "identity":{ + "correlationId":"blabla@ex.com", + "attr3":"three", + "attr4":"four", + "attr1":"High", + "attr2":"1.0", + "attr5":"Version 1.0" + } +} +``` + +
+ ## Testers ### List All Testers @@ -1478,7 +1583,7 @@ For multi-site accounts, account managers see logs across all related sites. Adm site string or int - Optional. Multi-site accounts only. Filter by site subdomain (e.g. site-1) or enterprise ID (e.g. 456). Must be within the caller's accessible sites. + Optional. Multi-site accounts only. Filter by site subdomain (for example, site-1) or enterprise ID (for example, 456). Must be in the caller's accessible sites. action_type @@ -1569,7 +1674,7 @@ For multi-site accounts, account managers see logs across all related sites. Adm site string or int - Optional. Multi-site accounts only. Filter by site subdomain (e.g. site-1) or enterprise ID (e.g. 456). Must be within the caller's accessible sites. + Optional. Multi-site accounts only. Filter by site subdomain (for example, site-1) or enterprise ID (for example, 456). Must be in the caller's accessible sites. action_type @@ -1660,7 +1765,7 @@ For multi-site accounts, account managers see logs across all related sites. Adm site string or int - Optional. Multi-site accounts only. Filter by site subdomain (e.g. site-1) or enterprise ID (e.g. 456). Must be within the caller's accessible sites. + Optional. Multi-site accounts only. Filter by site subdomain (for example, site-1) or enterprise ID (for example, 456). Must be in the caller's accessible sites. action_type diff --git a/docs/testfairy/api-reference/upload-api.md b/docs/testfairy/api-reference/upload-api.md index 724b72b21b..0434f3d915 100644 --- a/docs/testfairy/api-reference/upload-api.md +++ b/docs/testfairy/api-reference/upload-api.md @@ -287,6 +287,18 @@ In the case of an error, Sauce Labs Mobile App Distribution returns a JSON with +### Where Can I Find My API Key? + +To get your API KEY, open your account preferences at https://app.testfairy.com/settings/ and click on **TestFairy Access Key**. + +### How Can I Create a New API Key? + +To create a new API KEY, click on **Regenerate** on your account preferences page. + +### Why Is My API Key Empty? + +In cases Sauce Labs Mobile App Distribution identifies that by mistake, you initialize the SDK by using your API KEY instead of using your APP TOKEN, Sauce Labs Mobile App Distribution automatically reset the API KEY to protect your privacy. In this case, change the SDK initialization to use the APP TOKEN and create a new API KEY. + ### Can I Add Custom Metadata? Yes. Any POST parameter prefixed with "metadata." in the name is considered custom data and stored along with the upload. For example, consider this command: @@ -301,8 +313,8 @@ curl https://app.testfairy.com/api/upload \ Metadata is displayed and can be searched on in App Versions page by clicking on an app from the Dashboard. You can also view them on a single version's settings page. -### Can I attach the symbols file to my app to download it later? +### Can I Attach the Symbols File to My App to Download It Later? -Yes! You can attach your dSYM zipped for iOS / Text file .txt for Android app while uploading the app. Check out the [Upload/Download Symbols file] documentation for more details. +Yes. You can attach your dSYM zipped for iOS / Text file .txt for Android app while uploading the app. Check out the [Upload/Download Symbols file] documentation for more details. [Upload/Download Symbols file]: /testfairy/app-distribution/symbols-file/ diff --git a/docs/testfairy/app-center-alternative.md b/docs/testfairy/app-center-alternative.md index 5c074175bd..2a4b73e73e 100644 --- a/docs/testfairy/app-center-alternative.md +++ b/docs/testfairy/app-center-alternative.md @@ -6,17 +6,29 @@ sidebar_label: App Center Alternative import useBaseUrl from '@docusaurus/useBaseUrl'; -Welcome to Sauce Labs Mobile App Distribution, the App Center distribution alternative! Use our enterprise-grade app distribution capabilities to migrate from App Center. +Welcome to Sauce Labs Mobile App Distribution, the App Center distribution alternative. Use our enterprise-grade app distribution capabilities to migrate from App Center. ## Distribute App to Testers -Sauce Labs Mobile App Distribution offers enterprise-grade app distribution capabilities, allowing companies to easily and securely distribute the right apps to the right users. The platform allows admins to enforce corporate security policies during testing, and has the capability to automatically update apps to new versions or revoke access to installed apps or users. +Sauce Labs Mobile App Distribution offers enterprise-grade app distribution capabilities, allowing companies to securely distribute the right apps to the right users. The platform allows admins to enforce corporate security policies during testing, and has the capability to automatically update apps to new versions or revoke access to installed apps or users. For more information, see [Managing Testers](/testfairy/testers/managing-testers/). +## Analyze User Behavior + +Sauce Labs Mobile App Distribution provides mobile teams with videos showing exactly what happened during a mobile device test, before a crash, or before something went wrong, which ensures that bugs are fixed quickly. + +For more information, see [Adding the Sauce Mobile Beta SDK to your App](/app-distribution/sdk/adding-tf-sdk). + +## Collect Feedback + +Sauce Labs Mobile App Distribution's feedback features help users provide feedback by shaking their device or taking a screenshot. A report can automatically be posted to Jira along with the session video recording, logs, metrics, and crash reports. + +For more information, see [Getting Feedback](/app-distribution/sdk/user-feedback). + ## Security -Sauce Labs Mobile App Distribution is available as a private cloud or an on-premise installation and can integrate with any SAML single sign-on service. +Sauce Labs Mobile App Distribution is available as a private cloud or an on-premise installation and can integrate with any SAML single sign-on service. Sauce Labs Mobile App Distribution is the only platform that provides end-to-end data encryption using your private/public keys, so your data remains private. Sauce Labs Mobile App Distribution Public Cloud is available in the EU-Central-1 and US-East-1 data centers. The Private Cloud is available in various regions, customizable based on specific preferences. -For more information, see [Private Cloud](/testfairy/security/private-cloud/). +For more information, see [Private Cloud](/testfairy/security/private-cloud/) and [End to End Data Encryption](/app-distribution/sdk/security/data-encryption). diff --git a/docs/testfairy/app-distribution/ad-hoc-ipa.md b/docs/testfairy/app-distribution/ad-hoc-ipa.md new file mode 100644 index 0000000000..7e614c35a3 --- /dev/null +++ b/docs/testfairy/app-distribution/ad-hoc-ipa.md @@ -0,0 +1,80 @@ +--- +id: ad-hoc-ipa +title: Exporting Ad Hoc IPA +sidebar_label: Exporting Ad Hoc IPA +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +An Ad Hoc provisioning profile allows your app to be installed on designated devices without the assistance of Xcode. This distribution method lets you test your app on specific devices before it's released to the public. + +Apple allows app distribution for testing on registered devices using an **Ad-Hoc** or **Enterprise** provisioning profile. The output file you create is an iOS App file (a file with an `.ipa` filename extension) that is then used to install your app on registered devices. + +## Prerequisites + +Before proceeding with the steps below, ensure that you have completed the following: +1. **Register Test Devices** Register all the test devices on which you intend to install and test your app. You can follow the instructions in the [Registering Your iOS Device UDID Number](/testfairy/testers/reg-ios-device/) to complete this step. +2. **Create Ad Hoc Provisioning Profile** - Make sure you have created an Ad Hoc provisioning profile on the Apple Developer Portal. This provisioning profile should specify an App ID that matches your app, a list of test devices, and a single distribution certificate. +3. **Archive Your App** - Create an archive of your app using Xcode. This archive will be used to generate the Ad Hoc IPA file. + +## About Ad Hoc Provisioning Profiles + +An ad hoc provisioning profile is a distribution provisioning profile that allows your app to be installed on designated devices and to use app services without the assistance of Xcode. + +You can create four types of distribution provisioning profiles for apps: + +- **iOS App Store** - for distributing through the Apple App Store. + +- **Ad Hoc** - for installing on designated devices. See [Adding UDIDs to iOS Development Profile](/testfairy/app-distribution/adding-udids/). + +- **Enterprise** - for distributing an app in your organization. See the [Apple Developer Enterprise Program](https://developer.apple.com/programs/enterprise/). + +- **Development** - for distributing to members of your team. See [Apple Developer certificates](https://developer.apple.com/support/certificates/). + +Make sure you have created an Ad Hoc provisioning profile specifying an `App ID` that matches one or more of your apps, a set of **test devices**, and a single **distribution certificate** at the developer portal. For more information, see [Certificates, Identifiers and Profiles](https://developer.apple.com/account/resources/profiles/list) in the Apple Developer Portal. + +We recommend that companies apply to the [Apple Developer Enterprise Program](https://developer.apple.com/programs/enterprise/) and sign iOS apps for internal use with an Enterprise certificate. + +:::note +This document is not a legal resource. For the most accurate and up-to-date information, refer to Apple's official documentation and terms of service. +::: + +## Exporting Your App to an IPA (Ad Hoc Provisioning Profile) + +Follow these steps to export your app for testing using the Ad Hoc provisioning profile: + +1. Open your Xcode project. +2. In the Xcode project editor, select either `Generic iOS Device` or the name of your connected device from the Scheme toolbar menu. You cannot create an archive of a Simulator build. +3. Choose **Product** and then **Archive** from the top menu. The **Archives organizer** will appear, displaying the new archive. + Xcode will perform preliminary validation tests on the archive. To proceed with creating the IPA file, click **Distribute App**. +4. A dialog will appear with export options. Choose the `Save for Ad Hoc Deployment` option. This ensures that the app will be code signed with the distribution certificate. +5. In the next dialog, select a signing method and click "Next." +6. On the distribution options screen, customize the settings as needed and click "Next." +7. Review the app details, entitlements, and provisioning profile in the appearing dialog. +8. Click **Export**. The Finder will display the exported files. Save the exported IPA file to your desired location. + +## Installing Your App on Test Devices Using Xcode + +After exporting the Ad Hoc IPA file, you can install it on your registered test devices using Xcode: + +1. Connect the test device to your Mac using a USB cable. +2. Open Xcode on your Mac. +3. Choose **Window** and then **Devices** from the top menu. +4. In the **Devices** window, select the connected test device from the list. +5. In the Installed Apps section, click **+** to add an app. +6. In the dialog that appears, select the exported iOS App file (IPA) and click "Open." +7. Xcode will install the app on the test device, and it will appear in the list of installed apps. + +## Accessing Logs From Xcode + +Accessing raw logs on an iOS device requires hooking up that device via a USB cable to a computer. System logs often help a lot in debugging vague problems around app installation. + +The error displayed on an iOS device screen is often too generic, but the system logs explain more thoroughly the reason for the problem. To get the logs, complete the following: + +1. Open Xcode. +2. Open menu Window > Devices. +3. Select the device which you want to inspect. +4. Click on the **View Device Logs**. +5. A new window will open up with up-to-date logs from the device. diff --git a/docs/testfairy/app-distribution/adding-udids.md b/docs/testfairy/app-distribution/adding-udids.md new file mode 100644 index 0000000000..5a360739e4 --- /dev/null +++ b/docs/testfairy/app-distribution/adding-udids.md @@ -0,0 +1,50 @@ +--- +id: adding-udids +title: Adding UDIDs to iOS Development Profile +sidebar_label: Adding UDIDs +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +This documentation provides instructions on how to add testers' UDIDs (Unique Device Identifiers) to your iOS app development profile using Ad-Hoc certificates. This process enables you to distribute your app to specific devices for testing purposes. This guide pertains to Ad-Hoc certificates and assumes you have a basic understanding of the Apple Developer Portal and Sauce Labs Mobile App Distribution. + +:::note +While this guide offers general guidance, it is not a legal document. Always refer to Apple's official [iOS Developer Enterprise Program](https://developer.apple.com/programs/ios/enterprise/) for precise terms of service related to any Apple service. +::: + +## What You'll Need + +- An active Apple Developer account. +- Access to the Sauce Labs Mobile App Distribution platform for managing testers. +- A list of testers' UDIDs obtained through the Sauce Labs Mobile App Distribution platform. + +## Adding UDIDs to Your Development Profile + +To add a UDID to your Ad-Hoc certificate, follow these instructions: + +1. Open your Sauce Labs Mobile App Distribution [testers page](https://app.testfairy.com/testers) and invite new testers. You can add multiple addresses, one per line. + Your testers will get an email asking them to register their device. Once they register, you will get an email with your tester's UDID and their device details will be listed in https://app.testfairy.com/testers + +2. After you have all your testers' UDIDs [export their details](https://app.testfairy.com/testers/export/). + +3. Log in to the Apple Developer Portal and go to the [Devices area](https://developer.apple.com/account/resources/devices/list). + +4. Click on the + icon + +5. You may register one or multiple devices. + + 5.1 For adding one device add the device details in the **Register a Device** form. + + 5.2 Use **Register multiple devices** for registering devices from a file, choose your file and continue. + +6. Follow the on screen instructions. + +7. Click on **profiles** [distribution section](https://developer.apple.com/account/resources/profiles/list) + +8. Follow the on screen process to generate the profile. + +9. Generate the new profile and download the file. + +10. Make sure to delete the previous profile files. diff --git a/docs/testfairy/app-distribution/managing-dist.md b/docs/testfairy/app-distribution/managing-dist.md index 2f90774d5e..296d66ddec 100644 --- a/docs/testfairy/app-distribution/managing-dist.md +++ b/docs/testfairy/app-distribution/managing-dist.md @@ -8,16 +8,16 @@ import useBaseUrl from '@docusaurus/useBaseUrl'; import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -The distribution process begins once you have uploaded your application. +The distribution process begins after you have uploaded your application. -For Android applications, refer to [Uploading Apps](/testfairy/using-testfairy/uploading-an-app). +For Android applications, refer to [Uploading Apps](/testfairy/using-testfairy/uploading-an-app/). If you're distributing an iOS application: - When using an enterprise certificate, the process mirrors the Android distribution. -- With an ad-hoc certificate, you will need to add tester device UDIDs to your provisioning profile before distributing. +- With an ad-hoc certificate, follow the steps outlined in [Adding UDIDs to iOS Development Profile](/testfairy/app-distribution/adding-udids/). -If you're distributing a macOS application, first zip the application (before zipping, the file extension will be `.app`; after zipping, it will become `.zip`). Then, proceed with the process described in [Uploading Apps](/testfairy/using-testfairy/uploading-an-app). +If you're distributing a macOS application, first zip the application. Before zipping, the file extension is `.app`. After zipping, it is `.zip`. Then proceed with the process described in [Uploading Apps](/testfairy/using-testfairy/uploading-an-app/). Distributing your application is a straightforward process: @@ -37,7 +37,7 @@ To pre-invite testers, add them to the testers list on the [TESTERS](https://app inviting testers to a build -Pre-inviting is useful when distributing iOS applications to specific devices without an enterprise certificate. It is also commonly used for in-house testing teams. +Use pre-inviting when distributing iOS applications to specific devices without an enterprise certificate. It is also commonly used for in-house testing teams. ### Adding to a Specific Build @@ -49,7 +49,7 @@ This action sends them an email with a download link. It can be done with Androi ## Landing Pages -You can build a community of testers or distribute your app by utilizing a pre-designed landing page (see [Landing Pages](/testfairy/app-distribution/landing-pages)). Share the landing page link with testers to facilitate app downloads. The download link on the landing page can be secured through login, requiring testers to log in before downloading. +You can build a community of testers or distribute your app by utilizing a pre-designed landing page (see [Landing Pages](/testfairy/app-distribution/landing-pages/)). Share the landing page link with testers to facilitate app downloads. The download link on the landing page can be secured through login, requiring testers to log in before downloading. ## Testers Dashboard @@ -57,9 +57,9 @@ The **Testers Dashboard** is where a tester can view the apps they were invited ## Permissions -Permissions manage app distribution to groups of testers/users and are defined for each app (including all its builds). This means all testers within a group can access all builds of the app. +Permissions manage app distribution to groups of testers/users and are defined for each app (including all its builds). This means all testers in a group can access all builds of the app. -Permissions are based on defined groups of testers (see [Managing Testers](/testfairy/testers/managing-testers)). +Permissions are based on defined groups of testers (see [Managing Testers](/testfairy/testers/managing-testers/)). Once groups are defined, they will appear on the **Permissions** page. diff --git a/docs/testfairy/security/acct-settings.md b/docs/testfairy/security/acct-settings.md index 8cdc135f1b..8306e02bd0 100644 --- a/docs/testfairy/security/acct-settings.md +++ b/docs/testfairy/security/acct-settings.md @@ -12,14 +12,21 @@ To access the Settings page, click **Account** and then click **Sauce Labs Mobil Accessing Settings page -## Sauce Labs Mobile App Distribution Access Key +## SDK App Token -Sauce Labs Mobile App Distribution Access Key page +SDK App Token page +Your app token initializes the Sauce Mobile Beta SDK. See [Adding the Sauce Mobile Beta SDK to your App](/app-distribution/sdk/adding-tf-sdk) for more information. Your access key (API key) authenticates API requests. See [API Keys](/testfairy/security/api-keys) for usage details. For automated workflows, use dedicated [service accounts](/testfairy/security/service-accounts). +## Sauce Labs Mobile App Distribution Access Key + +Sauce Labs Mobile App Distribution Access Key page + +You can use the Sauce Labs Mobile App Distribution API to upload builds and invite testers directly. For more information, see [Upload API](/testfairy/api-reference/upload-api). + ## Sauce Labs Mobile App Distribution On the Sauce Labs Mobile App Distribution page you can access the **Rename version number if file already exists** setting. @@ -28,7 +35,7 @@ On the Sauce Labs Mobile App Distribution page you can access the **Rename versi ## Notifications -On the Notifications page, you can define what types of messages you want to receive about new builds. +On the Notifications page, you can define what types of messages you want to receive about new builds, crashes, and user feedback. Notifications page @@ -36,7 +43,7 @@ On the Notifications page, you can define what types of messages you want to rec You can integrate your Sauce Labs Mobile App Distribution account with different services to customize and streamline your work processes. -- Simple Mail Transfer Protocol (SMTP) and Gmail - See [SMTP and Gmail](/testfairy/integrations/smtp-gmail) for information about connecting your SMTP email server or Gmail account. This ensures you send the emails from the same account you used to register. +- SMTP and Gmail - See [SMTP and Gmail](/testfairy/integrations/smtp-gmail) for information about connecting your SMTP email server or Gmail account. This ensures you send the emails from the same account you used to register. - Slack - See [Slack](/testfairy/integrations/slack) for information about integrating your Slack account with Sauce Labs Mobile App Distribution. @@ -60,7 +67,7 @@ To ensure testers first login to their tester accounts before downloading your a ### SAML/Single Sign-On -Add the SSO metadata definitions file here. When you add SAML/Single Sign-On, the file contains your ID, URL, and X.509 certificate. See [SSO](/testfairy/security/sso/sso-intro) for more information. +Add the SSO metadata definitions file in this section. When you add SAML/Single Sign-On, the file contains your ID, URL, and X.509 certificate. See [SSO](/testfairy/security/sso/sso-intro) for more information. ## Account @@ -68,6 +75,7 @@ On the **Account** page you can access the following settings: - Change Timezone - Data Retention +- Session Quota Account page diff --git a/docs/testfairy/security/gdpr.md b/docs/testfairy/security/gdpr.md index a1f43834fb..d718f55c66 100644 --- a/docs/testfairy/security/gdpr.md +++ b/docs/testfairy/security/gdpr.md @@ -21,6 +21,10 @@ Yes. Sauce Labs Mobile App Distribution can provide a GDPR-compliant service to Yes. Sauce Labs Mobile App Distribution end-users are ensured that their data is only used by Sauce Labs Mobile App Distribution for legitimate purposes, meaning for the purpose intended by the developers who use our platform. We do not and will never sell, transfer or otherwise harm your data, and we make sure it is only used via our services. ,. +## Can Sauce Labs Mobile App Distribution End-Users See Their Sessions? + +Yes. On a Private cloud or on an on-prem installation, companies can allow their end-users to see their recorded sessions. The developer must identify the sessions using the SDK function [setUserId](/app-distribution/sdk/identifying-users). +Users can only see their sessions by logging into Sauce Labs Mobile App Distribution via their desktop browser. At the moment, mobile view is not supported. ## Can Sauce Labs Mobile App Distribution Users Request to Delete Their Sessions? diff --git a/docs/testfairy/testers/custom-ent-apps.md b/docs/testfairy/testers/custom-ent-apps.md new file mode 100644 index 0000000000..c159b4a70a --- /dev/null +++ b/docs/testfairy/testers/custom-ent-apps.md @@ -0,0 +1,25 @@ +--- +id: custom-ent-apps +title: Installing Custom Enterprise Apps +sidebar_label: Installing Custom Apps +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +Enterprise apps provide specialized functionalities tailored to the needs of an organization. These apps may not be available on the official app store but can be manually installed on your device. However, when you install such apps, you might encounter a notification stating that the app developer is not trusted on your device. This documentation will guide you through the process of manually installing and establishing trust for custom enterprise apps on your device. + +## Manually Install and Trust an Enterprise App + +When you first open an enterprise app that you've manually installed, you see a notification that the developer of the app isn't trusted on your device. You can dismiss this message, but then you can't open the app. Follow these steps to manually install and trust an enterprise app on your device: + +1. To establish trust for the app developer tap Settings → General → Profiles or Profiles and Device Management. + +2. Under the "Enterprise App" heading, you see a profile for the developer of the app you want to trust. + +3. Tap the name of the developer profile under the Enterprise App heading to establish trust for this developer. + +4. You will get a prompt to confirm your choice. + +5. After you trust this profile, you can install and run any other apps from the same developer. This developer will remain trusted until you remove all apps of this developer. diff --git a/docs/testfairy/testers/managing-testers.md b/docs/testfairy/testers/managing-testers.md index c4c9817554..198305f594 100644 --- a/docs/testfairy/testers/managing-testers.md +++ b/docs/testfairy/testers/managing-testers.md @@ -12,8 +12,8 @@ To see all the testers you have for your app, invite new testers, or import/expo alt upload -## Manage testers through the API -You can fully automate and control tester management through our APIs. For more details, refer to our [API documentation.](/testfairy/api-reference/rest-api/#testers). +## Manage Testers Through the API +You can fully automate and control tester management through our APIs. For more details, refer to our [API documentation](/testfairy/api-reference/rest-api/#testers). ## Inviting Testers by Email @@ -31,6 +31,8 @@ You can add testers manually or [import lists of testers](https://app.testfairy. :::note Only for iOS If you are **not** using an [iOS Enterprise certificate](https://developer.apple.com/programs/enterprise/), you will need to obtain the UDIDs (Unique Device Identifiers) of your testers' devices before sending them the app. The registration link in the email sent to testers will enable them to provide their UDIDs. These details will be added to your [testers page](https://app.testfairy.com/testers). +For more information on how to add UDIDs to provisioning profiles, see [Adding UDIDs to iOS Development Profile](/testfairy/app-distribution/adding-udids/). + ::: @@ -49,6 +51,6 @@ Tester groups help you manage the invitation process to your apps. If you want t ## Deleting Groups -To delete a group, you must first delete all its mentions in the GROUPS field. Delete the group by pressing the x next to its name. Once you have deleted all its occurrences, it will be deleted. Then, refresh the page to make sure it was deleted. +To delete a group, you must first delete all its mentions in the GROUPS field. Delete the group by pressing the x next to its name. After you have deleted all its occurrences, it will be deleted. Then, refresh the page to make sure it was deleted. Add multiple testers to the group diff --git a/docs/testfairy/testfairy.md b/docs/testfairy/testfairy.md index a64b71a36d..ec89b10eff 100644 --- a/docs/testfairy/testfairy.md +++ b/docs/testfairy/testfairy.md @@ -6,7 +6,7 @@ sidebar_label: Getting Started import useBaseUrl from '@docusaurus/useBaseUrl'; -Welcome to Sauce Labs Mobile App Distribution, the App Center distribution alternative! Use our enterprise-grade app distribution capabilities to migrate from App Center. +Welcome to Sauce Labs Mobile App Distribution, the App Center distribution alternative. Use our enterprise-grade app distribution capabilities to migrate from App Center. ## Store and Manage Apps @@ -18,19 +18,25 @@ For more information, see [Managing Apps through API](/testfairy/api-reference/r ## Distribute App to Testers -Sauce Labs Mobile App Distribution offers enterprise-grade app distribution capabilities, allowing companies to easily and securely distribute the right apps to users, such as internal or external beta testers. The platform allows administrators to enforce corporate security policies during testing, and can automatically update apps to new versions or revoke access to installed apps or users. +Sauce Labs Mobile App Distribution offers enterprise-grade app distribution capabilities, allowing companies to securely distribute the right apps to users, such as internal or external beta testers. The platform allows administrators to enforce corporate security policies during testing, and can automatically update apps to new versions or revoke access to installed apps or users. For more information, see [Managing Testers](/testfairy/testers/managing-testers/). -## Manage everything through API and Integrations +## Add the SDK to Your App -Sauce Labs Mobile App Distribution provides comprehensive API and integration capabilities, allowing companies to automate app management, user management, beta testers and seamlessly integrate with existing workflows. Use our APIs to manage app/user distribution or to enforce security policies programmatically. +Add the Sauce Mobile Beta SDK to your app to record tester sessions with video, collect in-app feedback, send remote logs, and identify users and sessions. The SDK does not install a crash handler. Crash reporting is provided by Backtrace (Sauce Labs Error Reporting), and the two SDKs are designed to run in the same app. + +The SDK is a beta release. Each SDK page carries a beta note. For more information, see [Adding the Sauce Mobile Beta SDK](/app-distribution/sdk/adding-tf-sdk) and [Using Sauce Mobile Beta with Backtrace](/app-distribution/sdk/backtrace-coexistence). + +## Manage Everything Through API and Integrations + +Sauce Labs Mobile App Distribution provides comprehensive API and integration capabilities, allowing companies to automate app management, user management, beta testers and seamlessly integrate with existing workflows. Use our APIs to manage app/user distribution or to enforce security policies programmatically. Integrate Sauce Labs Mobile App Distribution with your CI/CD pipelines, project management tools, and other enterprise systems to streamline your app development and distribution process. For more information, see [API Reference guide](/testfairy/api-reference/rest-api/) ## Security Sauce Labs Mobile App Distribution is available as a private cloud or an on-premise installation and can integrate with any SAML single-sign-on service. Sauce Labs Mobile App Distribution is the only platform that provides end-to-end data encryption using your private/public keys, so your data remains private. -Sauce Labs Mobile App Distribution Public Cloud is available in the EU-Central-1 and US-East-1 data centers. The Private Cloud is available in various regions, customizable based on specific preferences. +Sauce Labs Mobile App Distribution Public Cloud is available in the EU-Central-1 and US-East-1 data centers. The Private Cloud is available in various regions, customizable based on specific preferences. For more information, see [SSO](/testfairy/security/sso/sso-intro/), or [Private Cloud](/testfairy/security/private-cloud/). diff --git a/docs/testfairy/using-testfairy/dashboard.md b/docs/testfairy/using-testfairy/dashboard.md index 37a8d80472..789b4eb171 100644 --- a/docs/testfairy/using-testfairy/dashboard.md +++ b/docs/testfairy/using-testfairy/dashboard.md @@ -8,28 +8,47 @@ import useBaseUrl from '@docusaurus/useBaseUrl'; import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -The Sauce Labs Mobile App Distribution dashboard lets you view information about your uploaded apps. The dashboard includes a list of all uploaded apps and their app definitions. +The Sauce Labs Mobile App Distribution dashboard lets you view the information about your uploaded apps. The dashboard includes a list of all uploaded apps, their app definitions, the latest sessions recorded in your account, and separate tabs for crashes, feedback, and insights. -You can also upload apps from the dashboard using the **NEW UPLOAD** button and manage your users and account preferences. +You can also upload apps from the dashboard by clicking **NEW UPLOAD**, and manage your users and account preferences. ### DASHBOARD Tab dashboard general -The **DASHBOARD** tab displays the number of apps, testers, and users. +The **DASHBOARD** tab displays the number of test sessions, crashes, apps, testers, and users. #### Apps Table The **Apps** table lists all the apps loaded into the system. -The table displays information such as the app name and bundle ID, platform, latest version name and number, and the number of builds. You can also view the date the last build was uploaded. +The table displays information such as the app name and bundle ID, platform, latest version name and number, and details about the number of sessions, builds, crashes, and issues. You can also view the date the last build was uploaded and the date the latest session was logged. Clicking a row will open the **Builds** table (see [Builds](/testfairy/using-testfairy/builds)) for more information. Clicking the link button will open the app's landing page. +#### Recent Sessions + +A list of the recent sessions logged in the system. + ### **TESTERS** Tab dashboard testers The **TESTERS** tab displays your testers and assigned apps. See [Managing Testers](/testfairy/testers/managing-testers) for more information. + +### **CRASHES** Tab + +dashboard crashes + +The **CRASHES** tab displays crashes aggregated by stack trace. You can filter the list by app, version, and timeframe. +Each crash is aggregated after symbolication (if possible) and appears on a separate line. Unsymbolicated crashes may appear in different lines, one for each crash. + +### **USER FEEDBACK** Tab + +dashboard feedbacks + +The **USER FEEDBACK** tab displays user feedback submitted using the shake-to-report feature. The feedback is linked to the session in which it was created and contains: - A screenshot. - The text the user entered. - The reporter's email (which can be different from the user identified in the session). + +Each issue can be linked to a bug tracking system by clicking the **CREATE BUG** in the **ISSUE** column. Apps already connected to a bug-tracking system will display the issue link/number inside the button. diff --git a/docs/testfairy/using-testfairy/insights.md b/docs/testfairy/using-testfairy/insights.md new file mode 100644 index 0000000000..4084726250 --- /dev/null +++ b/docs/testfairy/using-testfairy/insights.md @@ -0,0 +1,59 @@ +--- +id: insights +title: Insights +sidebar_label: Insights +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +The **INSIGHTS** tab displays information about recorded crashes and sessions. + +## Preset Filters + +The **Preset Segments** column contains three preset filters: + +- Crashes: All crashes in the last month +- Slow Session: All slow sessions in the last month +- Received Feedback: All sessions with feedback in the last month + +## General Filters + +To filter the list using the following filters: +Filters + +You can see all apps in your account or filter only one of them: +filter by app name + +You can see all versions of the app you just filtered or a specific version. +filter by version + +You can also filter the timeframe of the list you want. +filter by timeframe + +## Attribute Filters + +In addition to general filters, you can also filter by **session attributes**. +filter by session attribute + +The default attributes are the primary attribute types (User, Session, Location, Device, etc.). + +## Custom Attributes Filters + +If you add [custom attributes](/app-distribution/sdk/session-attributes) to your app's session, you see them in this view and can filter according to their values: +filter by custom attribute + +This potent tool can help you identify specific sessions and filter them out of the many sessions in your app. + +:::tip +When you define values for this field, do not use characters other than text or numbers (a-z A-Z 0-9), such as `/`, `"`, or `-`, as they are not searchable in this field. If you do use these characters, you can search only for the parts of the text without them (for example, in `{attr1="dada-1270"}` the `-` is not searchable, so you can only search for `dada` or `1270`. +::: + +After you select an attribute, you can add a logical expression and enter a value in the text field. +logica expression + +You can see the result in the table. + +You can add another attribute filter using a plus sign or save a filter by clicking the **Save Segment**. After entering the name, you can see the segment in the **Custom Segments** list. +save segment diff --git a/docs/testfairy/using-testfairy/uploading-an-app.md b/docs/testfairy/using-testfairy/uploading-an-app.md index b9115634bc..54707a800d 100644 --- a/docs/testfairy/using-testfairy/uploading-an-app.md +++ b/docs/testfairy/using-testfairy/uploading-an-app.md @@ -26,7 +26,7 @@ The code of our command line uploader, Jenkins plugin, and Gradle plugin is open ### Expanding iOS Platform Support: Now Including VisionOS (XROS) -We now support VisionOS in addition to all other iOS platforms. Below is the complete list of platforms. Note that **XROS** is used to indicate support for VisionOS: +We now support VisionOS in addition to all other iOS platforms. Below is the complete list of platforms. **XROS** is used to indicate support for VisionOS: - iPhoneOS - WatchOS @@ -37,7 +37,7 @@ We now support VisionOS in addition to all other iOS platforms. Below is the com ### Supporting Both APK and AAB Formats for Android Apps -For Android apps, we support both APK and AAB formats. Here’s how it works: +For Android apps, we support both APK and AAB formats. The process works as follows: - **APK Support:** @@ -56,7 +56,7 @@ In the first stage, you need to select the file you want to upload. The supporte - **iOS:** `.ipa` - **Android:** `.apk` or `.aab` - **MacOS:** `.zip` -- **Windows:** Typically `.exe` or `.zip` (Please contact support for further assistance) +- **Windows:** Typically `.exe` or `.zip` (contact support for further assistance) Choose the appropriate file type based on your platform to proceed with the upload. @@ -64,8 +64,12 @@ Choose the appropriate file type based on your platform to proceed with the uplo You can define your build settings during the upload process: +-- **In-app reporting** - Check this box to enable/disable the "shake to report" feature in your app. When enabled, users can shake their device to send a feedback report, along with a video recording, screenshots, logs, and metrics of their test. + -- **Custom Comments** - Use this section to add release notes, describe the updates/changes, and anything else you want your testers to know. +**Note:** These settings are relevant only if you add the SDK to your app. + More build settings are available on the Account Settings page. ### Updating an App @@ -76,7 +80,7 @@ To update a build, upload the same file again (that is., the same build with the To upload a new build (a new version of the same app), upload the new version as you uploaded the old version. Sauce Labs Mobile App Distribution identifies that both apps have the same package name (bundle identifier) and group them together in the same project. -### Symbols or Mapping file +### Symbols or Mapping File In iOS and Android development, symbols or mapping files refer to files used to help debug and analyze crash reports. They are essential when apps are built with optimization settings, such as code obfuscation or stripping debug information, which make crash reports harder to read. -If you're using the API to upload your app, you can attach this file to your build for easy retrieval later if needed. +If you're using the API to upload your app, you can attach this file to your build for retrieval later if needed. diff --git a/sidebars.js b/sidebars.js index 4437f93d40..1cc21102c1 100644 --- a/sidebars.js +++ b/sidebars.js @@ -953,6 +953,83 @@ module.exports = { 'app-distribution/developer/api-migration-guide', ], }, + { + type: 'category', + label: 'Sauce Mobile SDK', + className: 'sidebar-beta', + collapsed: true, + items: [ + 'app-distribution/sdk/adding-tf-sdk', + 'app-distribution/sdk/backtrace-coexistence', + { + type: 'category', + label: 'Android', + collapsed: true, + items: [ + 'app-distribution/sdk/android/integrating-android', + ], + }, + { + type: 'category', + label: 'iOS', + collapsed: true, + items: [ + 'app-distribution/sdk/ios/integrating-ios', + 'app-distribution/sdk/ios/hiding-webview', + 'app-distribution/sdk/ios/log-network', + ], + }, + { + type: 'category', + label: 'React Native', + collapsed: true, + items: [ + 'app-distribution/sdk/react-native/react-native', + 'app-distribution/sdk/react-native/expo', + ], + }, + 'app-distribution/sdk/options', + 'app-distribution/sdk/session-attributes', + 'app-distribution/sdk/identifying-users', + 'app-distribution/sdk/user-feedback', + 'app-distribution/sdk/app-updates', + 'app-distribution/sdk/logging', + 'app-distribution/sdk/remote-logging', + 'app-distribution/sdk/map-locations', + 'app-distribution/sdk/private-cloud-int', + 'app-distribution/sdk/tf-production', + 'app-distribution/sdk/crash-handler-testing', + 'app-distribution/sdk/supported-platforms', + { + type: 'category', + label: 'Bug Tracking', + collapsed: true, + items: [ + 'app-distribution/sdk/bug-tracking/jira-cloud', + ], + }, + { + type: 'category', + label: 'Security', + collapsed: true, + items: [ + 'app-distribution/sdk/security/data-encryption', + 'app-distribution/sdk/security/hiding-data', + ], + }, + { + type: 'category', + label: 'Migrating from the TestFairy SDK', + collapsed: true, + items: [ + 'app-distribution/sdk/migration/overview', + 'app-distribution/sdk/migration/ios', + 'app-distribution/sdk/migration/android', + 'app-distribution/sdk/migration/react-native', + ], + }, + ], + }, { type: 'category', label: 'App Distribution', @@ -978,6 +1055,8 @@ module.exports = { collapsed: true, items: [ 'testfairy/app-distribution/managing-dist', + 'testfairy/app-distribution/ad-hoc-ipa', + 'testfairy/app-distribution/adding-udids', 'testfairy/app-distribution/app-versioning', 'testfairy/app-distribution/landing-pages', 'testfairy/app-distribution/app-expiration', @@ -995,6 +1074,7 @@ module.exports = { 'testfairy/testers/building-testers-app', 'testfairy/testers/testing-android-apps', 'testfairy/testers/reg-ios-device', + 'testfairy/testers/custom-ent-apps', 'testfairy/testers/testers-dashboard', ], }, diff --git a/src/css/custom.css b/src/css/custom.css index 2a1ed2b456..d490e1a7c9 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -1399,23 +1399,10 @@ html[data-theme="dark"] .role-badge { background-color: color-mix(in srgb, var(--role-color) 20%, transparent); } -/* "App Distribution (Classic)" sidebar category: de-emphasized, with a CLASSIC tag - so readers default to the current App Distribution docs. */ -.sidebar-classic > .menu__list-item-collapsible > .menu__link { - color: #8a9199; - font-weight: 500; -} - -.sidebar-classic > .menu__list-item-collapsible > .menu__link:hover, -html[data-theme="dark"] - .sidebar-classic - > .menu__list-item-collapsible - > .menu__link:hover { - color: #6b7280; -} - -/* The label sits in a CSS-module span inside the link; the link's own ::after is - already the collapse caret, so the tag hangs off the label instead. */ +/* "App Distribution (Classic)" sidebar category: a CLASSIC tag so readers default + to the current App Distribution docs. The label sits in a CSS-module span inside + the link; the link's own ::after is already the collapse caret, so the tag hangs + off the label instead. */ .sidebar-classic > .menu__list-item-collapsible > .menu__link @@ -1438,15 +1425,38 @@ html[data-theme="dark"] html[data-theme="dark"] .sidebar-classic > .menu__list-item-collapsible - > .menu__link { - color: #8a9199; + > .menu__link + [class*="categoryLinkLabel"]::after { + color: #f0a973; + background-color: rgba(232, 134, 61, 0.18); +} + +/* "Sauce Mobile SDK" sidebar category: a BETA tag next to the label, in the + same yellow as the in-page "Beta release" label (.sauceYellow). */ +.sidebar-beta + > .menu__list-item-collapsible + > .menu__link + [class*="categoryLinkLabel"]::after { + content: "BETA"; + display: inline-block; + margin-left: 0.45em; + padding: 0.05em 0.4em; + font-size: 0.62rem; + font-weight: 700; + letter-spacing: 0.04em; + line-height: 1.5; + vertical-align: middle; + border-radius: 4px; + color: var(--dark-300); + background-color: #fddf7f; + white-space: nowrap; } html[data-theme="dark"] - .sidebar-classic + .sidebar-beta > .menu__list-item-collapsible > .menu__link [class*="categoryLinkLabel"]::after { - color: #f0a973; - background-color: rgba(232, 134, 61, 0.18); + color: #fddf7f; + background-color: rgba(253, 223, 127, 0.18); } diff --git a/static/img/app-distribution/sdk/iphone-no-fields.png b/static/img/app-distribution/sdk/iphone-no-fields.png new file mode 100644 index 0000000000..e3b754bdbf Binary files /dev/null and b/static/img/app-distribution/sdk/iphone-no-fields.png differ diff --git a/static/img/app-distribution/sdk/iphone-with-fields.png b/static/img/app-distribution/sdk/iphone-with-fields.png new file mode 100644 index 0000000000..ecf0ba849a Binary files /dev/null and b/static/img/app-distribution/sdk/iphone-with-fields.png differ diff --git a/static/img/app-distribution/sdk/logHttp.png b/static/img/app-distribution/sdk/logHttp.png new file mode 100644 index 0000000000..ae39b11cd8 Binary files /dev/null and b/static/img/app-distribution/sdk/logHttp.png differ diff --git a/static/img/testfairy/platform/custom-import.png b/static/img/testfairy/platform/custom-import.png deleted file mode 100644 index d4046bdb3e..0000000000 Binary files a/static/img/testfairy/platform/custom-import.png and /dev/null differ diff --git a/static/img/testfairy/platform/custom_command.png b/static/img/testfairy/platform/custom_command.png deleted file mode 100644 index c29ee1ecb0..0000000000 Binary files a/static/img/testfairy/platform/custom_command.png and /dev/null differ diff --git a/static/img/testfairy/platform/file-select.png b/static/img/testfairy/platform/file-select.png deleted file mode 100644 index 2c6bae3189..0000000000 Binary files a/static/img/testfairy/platform/file-select.png and /dev/null differ diff --git a/static/img/testfairy/platform/only-when-installing.png b/static/img/testfairy/platform/only-when-installing.png deleted file mode 100644 index 171309fc40..0000000000 Binary files a/static/img/testfairy/platform/only-when-installing.png and /dev/null differ diff --git a/static/img/testfairy/platform/project_options.png b/static/img/testfairy/platform/project_options.png deleted file mode 100644 index 3766cdb9ef..0000000000 Binary files a/static/img/testfairy/platform/project_options.png and /dev/null differ diff --git a/static/img/testfairy/platform/step2.png b/static/img/testfairy/platform/step2.png deleted file mode 100644 index dc36dc790c..0000000000 Binary files a/static/img/testfairy/platform/step2.png and /dev/null differ diff --git a/static/img/testfairy/platform/step3.png b/static/img/testfairy/platform/step3.png deleted file mode 100644 index 5961a904e3..0000000000 Binary files a/static/img/testfairy/platform/step3.png and /dev/null differ diff --git a/static/img/testfairy/platform/step4.png b/static/img/testfairy/platform/step4.png deleted file mode 100644 index 20e03c0220..0000000000 Binary files a/static/img/testfairy/platform/step4.png and /dev/null differ diff --git a/static/img/testfairy/platform/unity-latest.png b/static/img/testfairy/platform/unity-latest.png deleted file mode 100644 index 117b306a87..0000000000 Binary files a/static/img/testfairy/platform/unity-latest.png and /dev/null differ diff --git a/static/img/testfairy/platform/upload_dsym_command.png b/static/img/testfairy/platform/upload_dsym_command.png deleted file mode 100644 index 29b3793cbc..0000000000 Binary files a/static/img/testfairy/platform/upload_dsym_command.png and /dev/null differ diff --git a/static/img/testfairy/platform/xamarin-insights-integration.png b/static/img/testfairy/platform/xamarin-insights-integration.png deleted file mode 100644 index 9ec03a306c..0000000000 Binary files a/static/img/testfairy/platform/xamarin-insights-integration.png and /dev/null differ diff --git a/static/img/testfairy/testing-an-app/bug-tracking/0-overview.png b/static/img/testfairy/testing-an-app/bug-tracking/0-overview.png deleted file mode 100644 index 78292fd2fe..0000000000 Binary files a/static/img/testfairy/testing-an-app/bug-tracking/0-overview.png and /dev/null differ diff --git a/static/img/testfairy/testing-an-app/bug-tracking/1-create-application-link.png b/static/img/testfairy/testing-an-app/bug-tracking/1-create-application-link.png deleted file mode 100644 index 8a5a71f1f3..0000000000 Binary files a/static/img/testfairy/testing-an-app/bug-tracking/1-create-application-link.png and /dev/null differ diff --git a/static/img/testfairy/testing-an-app/bug-tracking/2-continue.png b/static/img/testfairy/testing-an-app/bug-tracking/2-continue.png deleted file mode 100644 index 3d1b22e338..0000000000 Binary files a/static/img/testfairy/testing-an-app/bug-tracking/2-continue.png and /dev/null differ diff --git a/static/img/testfairy/testing-an-app/bug-tracking/3-setup-application-link.png b/static/img/testfairy/testing-an-app/bug-tracking/3-setup-application-link.png deleted file mode 100644 index 412c5e8ad3..0000000000 Binary files a/static/img/testfairy/testing-an-app/bug-tracking/3-setup-application-link.png and /dev/null differ diff --git a/static/img/testfairy/testing-an-app/bug-tracking/4-verify-access-token.png b/static/img/testfairy/testing-an-app/bug-tracking/4-verify-access-token.png deleted file mode 100644 index d64d56831b..0000000000 Binary files a/static/img/testfairy/testing-an-app/bug-tracking/4-verify-access-token.png and /dev/null differ diff --git a/static/img/testfairy/testing-an-app/bug-tracking/ALM-1.png b/static/img/testfairy/testing-an-app/bug-tracking/ALM-1.png deleted file mode 100644 index 2ca98e08ce..0000000000 Binary files a/static/img/testfairy/testing-an-app/bug-tracking/ALM-1.png and /dev/null differ diff --git a/static/img/testfairy/testing-an-app/bug-tracking/ALM-2.png b/static/img/testfairy/testing-an-app/bug-tracking/ALM-2.png deleted file mode 100644 index 57da4505ed..0000000000 Binary files a/static/img/testfairy/testing-an-app/bug-tracking/ALM-2.png and /dev/null differ diff --git a/static/img/testfairy/testing-an-app/bug-tracking/ALM-2_1.png b/static/img/testfairy/testing-an-app/bug-tracking/ALM-2_1.png deleted file mode 100644 index 1bfec3f014..0000000000 Binary files a/static/img/testfairy/testing-an-app/bug-tracking/ALM-2_1.png and /dev/null differ diff --git a/static/img/testfairy/testing-an-app/bug-tracking/ALM-3.png b/static/img/testfairy/testing-an-app/bug-tracking/ALM-3.png deleted file mode 100644 index 81a5ddee92..0000000000 Binary files a/static/img/testfairy/testing-an-app/bug-tracking/ALM-3.png and /dev/null differ diff --git a/static/img/testfairy/testing-an-app/bug-tracking/ALM-4.png b/static/img/testfairy/testing-an-app/bug-tracking/ALM-4.png deleted file mode 100644 index 1c054e1543..0000000000 Binary files a/static/img/testfairy/testing-an-app/bug-tracking/ALM-4.png and /dev/null differ diff --git a/static/img/testfairy/testing-an-app/bug-tracking/ALM-5.png b/static/img/testfairy/testing-an-app/bug-tracking/ALM-5.png deleted file mode 100644 index f439205d46..0000000000 Binary files a/static/img/testfairy/testing-an-app/bug-tracking/ALM-5.png and /dev/null differ diff --git a/static/img/testfairy/testing-an-app/bug-tracking/Github1.png b/static/img/testfairy/testing-an-app/bug-tracking/Github1.png deleted file mode 100644 index 96d4dc739d..0000000000 Binary files a/static/img/testfairy/testing-an-app/bug-tracking/Github1.png and /dev/null differ diff --git a/static/img/testfairy/testing-an-app/bug-tracking/Github2.png b/static/img/testfairy/testing-an-app/bug-tracking/Github2.png deleted file mode 100644 index 02741a16dd..0000000000 Binary files a/static/img/testfairy/testing-an-app/bug-tracking/Github2.png and /dev/null differ diff --git a/static/img/testfairy/testing-an-app/bug-tracking/Github2_1.png b/static/img/testfairy/testing-an-app/bug-tracking/Github2_1.png deleted file mode 100644 index 11c3b5188d..0000000000 Binary files a/static/img/testfairy/testing-an-app/bug-tracking/Github2_1.png and /dev/null differ diff --git a/static/img/testfairy/testing-an-app/bug-tracking/Github3.png b/static/img/testfairy/testing-an-app/bug-tracking/Github3.png deleted file mode 100644 index 4382a23354..0000000000 Binary files a/static/img/testfairy/testing-an-app/bug-tracking/Github3.png and /dev/null differ diff --git a/static/img/testfairy/testing-an-app/bug-tracking/trello1.png b/static/img/testfairy/testing-an-app/bug-tracking/trello1.png deleted file mode 100644 index 1e31986c8a..0000000000 Binary files a/static/img/testfairy/testing-an-app/bug-tracking/trello1.png and /dev/null differ diff --git a/static/img/testfairy/testing-an-app/bug-tracking/trello2.png b/static/img/testfairy/testing-an-app/bug-tracking/trello2.png deleted file mode 100644 index fd89cd77b4..0000000000 Binary files a/static/img/testfairy/testing-an-app/bug-tracking/trello2.png and /dev/null differ diff --git a/styles/sauce/Headings.yml b/styles/sauce/Headings.yml index 71abf3aba7..a3dce3e483 100644 --- a/styles/sauce/Headings.yml +++ b/styles/sauce/Headings.yml @@ -5,3 +5,12 @@ level: warning scope: heading match: $title style: AP +exceptions: + - iOS + - macOS + - dSYM + - dSYMs + - npm + - xcconfig + - Info.plist + - OAuth