diff --git a/.gitignore b/.gitignore
index c9a5fd928..80fbddcaa 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,6 +9,7 @@ dydxV4/dydxV4/_Configurations/credentials.json
dydxV4/dydxV4/_Configurations/GoogleService-Info-Staging.plist
dydxV4/dydxV4/_Configurations/GoogleService-Info.plist
scripts/secrets
+scripts/percy_test.sh
# fastlane specific
**/fastlane/report.xml
@@ -24,4 +25,4 @@ scripts/secrets
*.cer
*.mobileprovision
*.ipa
-*.dSYM.*
\ No newline at end of file
+*.dSYM.*
diff --git a/dydx/dydx.xcworkspace/xcshareddata/swiftpm/Package.resolved b/dydx/dydx.xcworkspace/xcshareddata/swiftpm/Package.resolved
index 78b51707e..5da496681 100644
--- a/dydx/dydx.xcworkspace/xcshareddata/swiftpm/Package.resolved
+++ b/dydx/dydx.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -1,4 +1,5 @@
{
+ "originHash" : "e085067eee7983e4ba42c06fd96e9ea3884445575db37e125bed61bde5eeb3f1",
"pins" : [
{
"identity" : "bigint",
@@ -36,6 +37,15 @@
"version" : "2.0.2"
}
},
+ {
+ "identity" : "percy-xcui-swift",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/percy/percy-xcui-swift",
+ "state" : {
+ "revision" : "a2e9a86dfc3f5b69ef53cbda28a0ea71098c9f77",
+ "version" : "1.0.0"
+ }
+ },
{
"identity" : "qrcode",
"kind" : "remoteSourceControl",
@@ -226,5 +236,5 @@
}
}
],
- "version" : 2
+ "version" : 3
}
diff --git a/dydx/dydxTests/Info.plist b/dydx/dydxTests/Info.plist
deleted file mode 100644
index 6c40a6cd0..000000000
--- a/dydx/dydxTests/Info.plist
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
- CFBundleDevelopmentRegion
- $(DEVELOPMENT_LANGUAGE)
- CFBundleExecutable
- $(EXECUTABLE_NAME)
- CFBundleIdentifier
- $(PRODUCT_BUNDLE_IDENTIFIER)
- CFBundleInfoDictionaryVersion
- 6.0
- CFBundleName
- $(PRODUCT_NAME)
- CFBundlePackageType
- BNDL
- CFBundleShortVersionString
- 1.0
- CFBundleVersion
- 1
-
-
diff --git a/dydx/dydxTests/dydxTests.swift b/dydx/dydxTests/dydxTests.swift
deleted file mode 100644
index 8ced22913..000000000
--- a/dydx/dydxTests/dydxTests.swift
+++ /dev/null
@@ -1,12 +0,0 @@
-//
-// dydxTests.swift
-// dydxTests
-//
-// Created by Qiang Huang on 10/8/18.
-// Copyright © 2018 dYdX. All rights reserved.
-//
-
-import XCTest
-
-class dydxTests: XCTestCase {
-}
diff --git a/dydxV4/dydxV4.xcodeproj/project.pbxproj b/dydxV4/dydxV4.xcodeproj/project.pbxproj
index b196c103d..8bcf26984 100644
--- a/dydxV4/dydxV4.xcodeproj/project.pbxproj
+++ b/dydxV4/dydxV4.xcodeproj/project.pbxproj
@@ -24,6 +24,9 @@
029E11F029ADB9C600FE271C /* dydxChart.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 029E11EB29ADB9BC00FE271C /* dydxChart.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
02A5BB9F2A04617800BEE77E /* dydxAbacusDataLocalizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02A5BB9E2A04617800BEE77E /* dydxAbacusDataLocalizer.swift */; };
02C7262B29D793C90098AFBC /* dydxCompositeTracking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02C7262A29D793C90098AFBC /* dydxCompositeTracking.swift */; };
+ 02DA0AFA2B57609100BFA975 /* dydxV4UITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DA0AF92B57609100BFA975 /* dydxV4UITests.swift */; };
+ 02DA0AFC2B57609100BFA975 /* dydxV4UITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DA0AFB2B57609100BFA975 /* dydxV4UITestsLaunchTests.swift */; };
+ 02DA0B062B57633E00BFA975 /* PercyXcui in Frameworks */ = {isa = PBXBuildFile; productRef = 02DA0B052B57633E00BFA975 /* PercyXcui */; };
02E7DDDF28CFB33B00727949 /* dydxStateManager.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 02E7DDA828CFB31000727949 /* dydxStateManager.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
02FF092E29ADBD9900781EDA /* dydxFormatter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 028420CF29AD77F400C0E7CC /* dydxFormatter.framework */; };
02FF092F29ADBD9F00781EDA /* dydxChart.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 029E11EB29ADB9BC00FE271C /* dydxChart.framework */; };
@@ -244,6 +247,13 @@
remoteGlobalIDString = 029E0FD429ADB41400FE271C;
remoteInfo = dydxChart;
};
+ 02DA0AFD2B57609100BFA975 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 3112F457216BBF8400708927 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 3112F45E216BBF8400708927;
+ remoteInfo = dydxV4;
+ };
02E7DDA728CFB31000727949 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 02E7DD9A28CFB31000727949 /* dydxStateManager.xcodeproj */;
@@ -804,6 +814,10 @@
029E11E529ADB9BC00FE271C /* dydxChart.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = dydxChart.xcodeproj; path = ../dydx/dydxChart/dydxChart.xcodeproj; sourceTree = ""; };
02A5BB9E2A04617800BEE77E /* dydxAbacusDataLocalizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxAbacusDataLocalizer.swift; sourceTree = ""; };
02C7262A29D793C90098AFBC /* dydxCompositeTracking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxCompositeTracking.swift; sourceTree = ""; };
+ 02DA0AF72B57609100BFA975 /* dydxV4UITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = dydxV4UITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+ 02DA0AF92B57609100BFA975 /* dydxV4UITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxV4UITests.swift; sourceTree = ""; };
+ 02DA0AFB2B57609100BFA975 /* dydxV4UITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxV4UITestsLaunchTests.swift; sourceTree = ""; };
+ 02DA0B022B57622B00BFA975 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Platforms/iPhoneOS.platform/Developer/Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; };
02E7DD9A28CFB31000727949 /* dydxStateManager.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = dydxStateManager.xcodeproj; path = ../dydx/dydxStateManager/dydxStateManager.xcodeproj; sourceTree = ""; };
171807B0CCAE5C86B5D65793 /* Pods_iOS_dydxV4.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iOS_dydxV4.framework; sourceTree = BUILT_PRODUCTS_DIR; };
27A7967A2A620BF6007C3D04 /* dydxSettingsStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxSettingsStore.swift; sourceTree = ""; };
@@ -855,6 +869,14 @@
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
+ 02DA0AF42B57609100BFA975 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 02DA0B062B57633E00BFA975 /* PercyXcui in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
3112F45C216BBF8400708927 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@@ -1028,6 +1050,15 @@
name = Products;
sourceTree = "";
};
+ 02DA0AF82B57609100BFA975 /* dydxV4UITests */ = {
+ isa = PBXGroup;
+ children = (
+ 02DA0AF92B57609100BFA975 /* dydxV4UITests.swift */,
+ 02DA0AFB2B57609100BFA975 /* dydxV4UITestsLaunchTests.swift */,
+ );
+ path = dydxV4UITests;
+ sourceTree = "";
+ };
02E7DD9B28CFB31000727949 /* Products */ = {
isa = PBXGroup;
children = (
@@ -1040,6 +1071,7 @@
0904864F08DCEDE87F5B4956 /* Frameworks */ = {
isa = PBXGroup;
children = (
+ 02DA0B022B57622B00BFA975 /* XCTest.framework */,
646D73AB2A4F434300EDF2E5 /* dydxAbacusBridging.framework */,
31B77C2422DAD0B3004F9B21 /* dydxUI.framework */,
31E65C08216BCB01008ABEE9 /* Utilities.framework */,
@@ -1071,6 +1103,7 @@
31C138BF23DE044C0064B5F5 /* Shared */,
3112F461216BBF8400708927 /* dydx */,
3112F478216BBF8600708927 /* dydxTests */,
+ 02DA0AF82B57609100BFA975 /* dydxV4UITests */,
);
sourceTree = "";
};
@@ -1079,6 +1112,7 @@
children = (
3112F45F216BBF8400708927 /* dydxV4.app */,
3112F475216BBF8600708927 /* dydxV4Tests.xctest */,
+ 02DA0AF72B57609100BFA975 /* dydxV4UITests.xctest */,
);
name = Products;
sourceTree = "";
@@ -1308,6 +1342,27 @@
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
+ 02DA0AF62B57609100BFA975 /* dydxV4UITests */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 02DA0AFF2B57609100BFA975 /* Build configuration list for PBXNativeTarget "dydxV4UITests" */;
+ buildPhases = (
+ 02DA0AF32B57609100BFA975 /* Sources */,
+ 02DA0AF42B57609100BFA975 /* Frameworks */,
+ 02DA0AF52B57609100BFA975 /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 02DA0AFE2B57609100BFA975 /* PBXTargetDependency */,
+ );
+ name = dydxV4UITests;
+ packageProductDependencies = (
+ 02DA0B052B57633E00BFA975 /* PercyXcui */,
+ );
+ productName = dydxV4UITests;
+ productReference = 02DA0AF72B57609100BFA975 /* dydxV4UITests.xctest */;
+ productType = "com.apple.product-type.bundle.ui-testing";
+ };
3112F45E216BBF8400708927 /* dydxV4 */ = {
isa = PBXNativeTarget;
buildConfigurationList = 3112F489216BBF8600708927 /* Build configuration list for PBXNativeTarget "dydxV4" */;
@@ -1353,6 +1408,8 @@
31B77BCA22DAD077004F9B21 /* PBXTargetDependency */,
);
name = dydxV4;
+ packageProductDependencies = (
+ );
productName = dydx;
productReference = 3112F45F216BBF8400708927 /* dydxV4.app */;
productType = "com.apple.product-type.application";
@@ -1383,10 +1440,14 @@
3112F457216BBF8400708927 /* Project object */ = {
isa = PBXProject;
attributes = {
- LastSwiftUpdateCheck = 1330;
+ LastSwiftUpdateCheck = 1510;
LastUpgradeCheck = 1400;
ORGANIZATIONNAME = "dYdX Trading Inc";
TargetAttributes = {
+ 02DA0AF62B57609100BFA975 = {
+ CreatedOnToolsVersion = 15.1;
+ TestTargetID = 3112F45E216BBF8400708927;
+ };
3112F45E216BBF8400708927 = {
CreatedOnToolsVersion = 10.0;
LastSwiftMigration = 1020;
@@ -1430,6 +1491,9 @@
"zh-Hans",
);
mainGroup = 3112F456216BBF8400708927;
+ packageReferences = (
+ 02DA0B042B57633E00BFA975 /* XCRemoteSwiftPackageReference "percy-xcui-swift" */,
+ );
productRefGroup = 3112F460216BBF8400708927 /* Products */;
projectDirPath = "";
projectReferences = (
@@ -1526,6 +1590,7 @@
targets = (
3112F45E216BBF8400708927 /* dydxV4 */,
3112F474216BBF8600708927 /* dydxV4Tests */,
+ 02DA0AF62B57609100BFA975 /* dydxV4UITests */,
);
};
/* End PBXProject section */
@@ -2031,6 +2096,13 @@
/* End PBXReferenceProxy section */
/* Begin PBXResourcesBuildPhase section */
+ 02DA0AF52B57609100BFA975 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
3112F45D216BBF8400708927 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
@@ -2243,6 +2315,15 @@
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
+ 02DA0AF32B57609100BFA975 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 02DA0AFA2B57609100BFA975 /* dydxV4UITests.swift in Sources */,
+ 02DA0AFC2B57609100BFA975 /* dydxV4UITestsLaunchTests.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
3112F45B216BBF8400708927 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
@@ -2310,6 +2391,11 @@
name = dydxChart;
targetProxy = 029E11EE29ADB9BF00FE271C /* PBXContainerItemProxy */;
};
+ 02DA0AFE2B57609100BFA975 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 3112F45E216BBF8400708927 /* dydxV4 */;
+ targetProxy = 02DA0AFD2B57609100BFA975 /* PBXContainerItemProxy */;
+ };
02E7DDDE28CFB32D00727949 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
name = dydxStateManager;
@@ -2404,6 +2490,55 @@
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
+ 02DA0B002B57609100BFA975 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ DEVELOPMENT_TEAM = 75C6UARB5H;
+ ENABLE_USER_SCRIPT_SANDBOXING = NO;
+ GCC_C_LANGUAGE_STANDARD = gnu17;
+ GENERATE_INFOPLIST_FILE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 15.0;
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = exchange.dydx.dydxV4UITests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
+ SWIFT_EMIT_LOC_STRINGS = NO;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ TEST_TARGET_NAME = dydxV4;
+ };
+ name = Debug;
+ };
+ 02DA0B012B57609100BFA975 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ DEVELOPMENT_TEAM = 75C6UARB5H;
+ ENABLE_USER_SCRIPT_SANDBOXING = NO;
+ GCC_C_LANGUAGE_STANDARD = gnu17;
+ GENERATE_INFOPLIST_FILE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 15.0;
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = exchange.dydx.dydxV4UITests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_EMIT_LOC_STRINGS = NO;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ TEST_TARGET_NAME = dydxV4;
+ };
+ name = Release;
+ };
3112F487216BBF8600708927 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@@ -2617,6 +2752,15 @@
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
+ 02DA0AFF2B57609100BFA975 /* Build configuration list for PBXNativeTarget "dydxV4UITests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 02DA0B002B57609100BFA975 /* Debug */,
+ 02DA0B012B57609100BFA975 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
3112F45A216BBF8400708927 /* Build configuration list for PBXProject "dydxV4" */ = {
isa = XCConfigurationList;
buildConfigurations = (
@@ -2645,6 +2789,25 @@
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
+
+/* Begin XCRemoteSwiftPackageReference section */
+ 02DA0B042B57633E00BFA975 /* XCRemoteSwiftPackageReference "percy-xcui-swift" */ = {
+ isa = XCRemoteSwiftPackageReference;
+ repositoryURL = "https://github.com/percy/percy-xcui-swift";
+ requirement = {
+ kind = exactVersion;
+ version = 1.0.0;
+ };
+ };
+/* End XCRemoteSwiftPackageReference section */
+
+/* Begin XCSwiftPackageProductDependency section */
+ 02DA0B052B57633E00BFA975 /* PercyXcui */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = 02DA0B042B57633E00BFA975 /* XCRemoteSwiftPackageReference "percy-xcui-swift" */;
+ productName = PercyXcui;
+ };
+/* End XCSwiftPackageProductDependency section */
};
rootObject = 3112F457216BBF8400708927 /* Project object */;
}
diff --git a/dydxV4/dydxV4.xcodeproj/xcshareddata/xcschemes/dydxV4.xcscheme b/dydxV4/dydxV4.xcodeproj/xcshareddata/xcschemes/dydxV4.xcscheme
index 9ad29e3e4..7e6bd3605 100644
--- a/dydxV4/dydxV4.xcodeproj/xcshareddata/xcschemes/dydxV4.xcscheme
+++ b/dydxV4/dydxV4.xcodeproj/xcshareddata/xcschemes/dydxV4.xcscheme
@@ -10,7 +10,7 @@
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
+ scriptText = "# Type a script or drag a script file from your workspace to insert its path.
### find ${DWARF_DSYM_FOLDER_PATH} -name "*.dSYM" | xargs -I \{\} ${PODS_ROOT}/Fabric/upload-symbols -gsp ${PROJECT_DIR}/_Configurations/GoogleService-Info.plist -p ios \{\}
">
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dydxV4/dydxV4.xcodeproj/xcshareddata/xcschemes/dydxV4UITests.xcscheme b/dydxV4/dydxV4.xcodeproj/xcshareddata/xcschemes/dydxV4UITests.xcscheme
new file mode 100644
index 000000000..d0f75f6b1
--- /dev/null
+++ b/dydxV4/dydxV4.xcodeproj/xcshareddata/xcschemes/dydxV4UITests.xcscheme
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dydxV4/dydxV4UITests/dydxV4UITests.swift b/dydxV4/dydxV4UITests/dydxV4UITests.swift
new file mode 100644
index 000000000..90e00be56
--- /dev/null
+++ b/dydxV4/dydxV4UITests/dydxV4UITests.swift
@@ -0,0 +1,54 @@
+//
+// dydxV4UITests.swift
+// dydxV4UITests
+//
+// Created by Rui Huang on 1/16/24.
+// Copyright © 2024 dYdX Trading Inc. All rights reserved.
+//
+
+import XCTest
+import PercyXcui
+
+final class dydxV4UITests: XCTestCase {
+
+ override func setUp() {
+ super.setUp()
+
+ // Put setup code here. This method is called before the invocation of each test method in the class.
+
+ // In UI tests it is usually best to stop immediately when a failure occurs.
+ continueAfterFailure = false
+ // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method.
+ XCUIApplication().launch()
+
+ // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
+ }
+
+ override func tearDown() {
+ // Put teardown code here. This method is called after the invocation of each test method in the class.
+ super.tearDown()
+ }
+
+ func testExample() throws {
+ // UI tests must launch the application that they test.
+ let app = XCUIApplication()
+
+ // take screenshot
+ let appPercy = AppPercy()
+
+ do {
+ try appPercy.screenshot(name: "First Screenshot")
+ } catch {
+ NSLog("App percy screenshot failed")
+ }
+ }
+
+// func testLaunchPerformance() throws {
+// if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) {
+// // This measures how long it takes to launch your application.
+// measure(metrics: [XCTApplicationLaunchMetric()]) {
+// XCUIApplication().launch()
+// }
+// }
+// }
+}
diff --git a/dydxV4/dydxV4UITests/dydxV4UITestsLaunchTests.swift b/dydxV4/dydxV4UITests/dydxV4UITestsLaunchTests.swift
new file mode 100644
index 000000000..93f02dde9
--- /dev/null
+++ b/dydxV4/dydxV4UITests/dydxV4UITestsLaunchTests.swift
@@ -0,0 +1,33 @@
+//
+// dydxV4UITestsLaunchTests.swift
+// dydxV4UITests
+//
+// Created by Rui Huang on 1/16/24.
+// Copyright © 2024 dYdX Trading Inc. All rights reserved.
+//
+
+import XCTest
+
+final class dydxV4UITestsLaunchTests: XCTestCase {
+
+ override class var runsForEachTargetApplicationUIConfiguration: Bool {
+ true
+ }
+
+ override func setUpWithError() throws {
+ continueAfterFailure = false
+ }
+
+// func testLaunch() throws {
+// let app = XCUIApplication()
+// app.launch()
+//
+// // Insert steps here to perform after app launch but before taking a screenshot,
+// // such as logging into a test account or navigating somewhere in the app
+//
+// let attachment = XCTAttachment(screenshot: app.screenshot())
+// attachment.name = "Launch Screen"
+// attachment.lifetime = .keepAlways
+// add(attachment)
+// }
+}
diff --git a/scripts/percy_create_test_runner.sh b/scripts/percy_create_test_runner.sh
new file mode 100755
index 000000000..67d06dcba
--- /dev/null
+++ b/scripts/percy_create_test_runner.sh
@@ -0,0 +1,43 @@
+#!/bin/sh
+
+# Create UI Test Runner for Percy via xcodebuild. The output is a zip file that contains the UI Test Runner.
+# Output file: /dydxV4UITests.zip
+#
+# Note this script is intended to be run from a development machine, not from a CI/CD environment.
+# For CI/CD, you will need to run the CI/CD step to build the project and extract the UI Test Runner.
+
+if [ -z "$1" ]; then
+ echo "Please provide the output folder path as the first argument"
+ exit 1
+fi
+
+TMP_FOLDER=$1
+
+xcodebuild -workspace "dydx/dydx.xcworkspace" \
+ -scheme "dydxV4UITests" -derivedDataPath $TMP_FOLDER/dydxV4UITests \
+ -configuration Debug -sdk iphoneos build-for-testing
+
+derived_data_path=$TMP_FOLDER/dydxV4UITests/Build
+
+# # get the DerivedData folder
+# derived_data_path=$(xcodebuild -workspace "dydx/dydx.xcworkspace" -scheme "dydxV4" -showBuildSettings | grep OBJROOT | cut -d "=" -f 2 - | sed 's/^ *//')
+
+# # remove the last part of the path
+# derived_data_path=$(dirname $derived_data_path)
+
+app_path="$derived_data_path/Products/Debug-iphoneos/dydxV4UITests-Runner.app"
+
+if [ ! -d "$app_path" ]; then
+ echo "App path not found: $app_path"
+ exit 1
+fi
+
+if [ -f /tmp/dydxV4UITests.zip ]; then
+ rm $TMP_FOLDER/dydxV4UITests.zip
+fi
+
+cwd=$(pwd)
+cp -r "$app_path" $TMP_FOLDER/dydxV4UITests-Runner.app
+cd $TMP_FOLDER
+zip -r dydxV4UITests.zip dydxV4UITests-Runner.app/
+cd $cwd
\ No newline at end of file
diff --git a/scripts/percy_extract_test_runner.sh b/scripts/percy_extract_test_runner.sh
new file mode 100755
index 000000000..d06e419bd
--- /dev/null
+++ b/scripts/percy_extract_test_runner.sh
@@ -0,0 +1,43 @@
+#!/bin/sh
+
+# Extract the test runner from the test bunldle .zip file created by CI/CD
+# Output file: /dydxV4UITests.zip
+#
+# Usage: Run this script with the path to the test bundle .zip file as the first argument
+
+if [ -z "$1" ]; then
+ echo "Please provide the test bundle .zip file path as the first argument"
+ exit 1
+fi
+
+TEST_BUNDLE_PATH=$1
+
+if [ -z "$2" ]; then
+ echo "Please provide the output folder path as the second argument"
+ exit 1
+fi
+
+TMP_FOLDER=$2
+
+TMP_PATH=$TMP_FOLDER/dydxV4TestBundle.zip
+
+if [ -f $TMP_PATH ]; then
+ rm $TMP_PATH
+fi
+
+cp $TEST_BUNDLE_PATH $TMP_PATH
+
+pwd=$(pwd)
+
+cd $TMP_FOLDER
+
+if [ -d test_bundle ]; then
+ rm -rf test_bundle
+fi
+unzip $TMP_PATH -d test_bundle
+cp -r test_bundle/Debug-iphoneos/dydxV4UITests-Runner.app $TMP_FOLDER/dydxV4UITests-Runner.app
+zip -r dydxV4UITests.zip dydxV4UITests-Runner.app/
+
+cd $pwd
+
+echo $TMP_FOLDER/dydxV4UITests.zip
diff --git a/scripts/percy_run_build.sh b/scripts/percy_run_build.sh
new file mode 100755
index 000000000..1ed468df1
--- /dev/null
+++ b/scripts/percy_run_build.sh
@@ -0,0 +1,55 @@
+#!/bin/sh
+
+# https://www.browserstack.com/docs/app-percy/sample-build/xcuitest
+
+# run Percy build
+
+# Usage: Run this script with the Percy user as the first argument
+# e.g. ./percy_run_build.sh ruihuang_ry52wv:HXRCy79y5SDuDvvQw6Qw
+
+if [ -z "$1" ]; then
+ echo "Please provide the Percy user as the first argument"
+ exit 1
+fi
+
+if [ -z "$2" ]; then
+ echo "Please provide the PERCY_BUILD_URL as the second argument"
+ exit 1
+fi
+
+if [ -z "$3" ]; then
+ echo "Please provide the PERCY_TEST_SUITE_URL as the third argument"
+ exit 1
+fi
+
+if [ -z "$4" ]; then
+ echo "Please provide the PERCY_TOKEN as the fourth argument"
+ exit 1
+fi
+
+PERCY_USER=$1
+PERCY_BUILD_URL=$2
+PERCY_TEST_SUITE_URL=$3
+PERCY_TOKEN=$4
+PERCY_BRANCH=$5
+if [ -z "$5" ]; then
+ PERCY_BRANCH=""
+fi
+PERCY_COMMIT=$6
+if [ -z "$6" ]; then
+ PERCY_COMMIT=""
+fi
+PERCY_PULL_REQUEST=$7
+
+JSON_FMT='{
+ "devices": ["iPhone 15 Pro-17"],
+ "debugscreenshots": "true",
+ "appPercy": { "env": {"PERCY_BRANCH": "%s", "PERCY_COMMIT": "%s" }, "PERCY_TOKEN": "%s"},
+ "app": "%s", "testSuite": "%s"}'
+PAYLOAD=$(printf "$JSON_FMT" "$PERCY_BRANCH" "$PERCY_COMMIT" "$PERCY_TOKEN" "$PERCY_BUILD_URL" "$PERCY_TEST_SUITE_URL")
+echo $PAYLOAD
+
+curl -u $PERCY_USER \
+-X POST "https://api-cloud.browserstack.com/app-automate/xcuitest/v2/build" \
+-d "$PAYLOAD" \
+-H "Content-Type: application/json"
\ No newline at end of file
diff --git a/scripts/percy_upload_ipa.sh b/scripts/percy_upload_ipa.sh
new file mode 100755
index 000000000..57702aea5
--- /dev/null
+++ b/scripts/percy_upload_ipa.sh
@@ -0,0 +1,43 @@
+#!/bin/sh
+
+# https://www.browserstack.com/docs/app-percy/sample-build/xcuitest
+
+# upload IPA to percy
+
+# Usage: Run this script with the Percy user id as the first argument and the path to the IPA as the second argument
+# e.g. ./percy_upload_ipa.sh ruihuang_ry52wv:HXRCy79y5SDuDvvQw6Qw /path/to/dydxV4.ipa
+
+unset PERCY_BUILD_URL
+
+if [ -z "$1" ]; then
+ echo "Please provide the Percy user as the first argument"
+ exit 1
+fi
+
+if [ -z "$2" ]; then
+ echo "Please provide the IPA path as the second argument"
+ exit 1
+fi
+
+PERCY_USER=$1
+IPA_PATH=$2
+
+# create a temporary name
+TMP_FOLDER=$(mktemp -d -q /tmp/ipa.XXXXXX)
+if [ $? -ne 0 ]; then
+ echo "$0: Can't create temp folder, bye.."
+ exit 1
+fi
+
+cp $IPA_PATH $TMP_FOLDER/dydxV4.ipa
+
+response=`curl -u $PERCY_USER \
+ -X POST "https://api-cloud.browserstack.com/app-automate/xcuitest/v2/app" \
+ -F "file=@$TMP_FOLDER/dydxV4.ipa"`
+
+# read response as JSON and extract the "app_url" field
+app_url=`echo $response | jq -r .app_url`
+echo "App URL: $app_url"
+
+# export the app URL as an environment variable
+export PERCY_BUILD_URL=$app_url
diff --git a/scripts/percy_upload_test_runner.sh b/scripts/percy_upload_test_runner.sh
new file mode 100755
index 000000000..bbb9a4f29
--- /dev/null
+++ b/scripts/percy_upload_test_runner.sh
@@ -0,0 +1,36 @@
+#!/bin/sh
+
+# https://www.browserstack.com/docs/app-percy/sample-build/xcuitest
+
+# upload UI Test Runner to percy
+
+# Usage: Run this script with the Percy user as the first argument
+# e.g. ./percy_upload_test_runner.sh ruihuang_ry52wv:HXRCy79y5SDuDvvQw6Qw
+
+
+unset PERCY_TEST_SUITE_URL
+
+if [ -z "$1" ]; then
+ echo "Please provide the Percy user as the first argument"
+ exit 1
+fi
+
+PERCY_USER=$1
+TEST_BUNDLE_PATH=$2
+
+if [ $TEST_BUNDLE_PATH != "/tmp/dydxV4UITests.zip" ]; then
+ cp $TEST_BUNDLE_PATH /tmp/dydxV4UITests.zip
+fi
+
+response=`curl -u $PERCY_USER \
+ -X POST "https://api-cloud.browserstack.com/app-automate/xcuitest/v2/test-suite" \
+ -F "file=@/tmp/dydxV4UITests.zip"`
+
+echo $response
+
+# read response as JSON and extract the "test_suite_url" field
+test_suite_url=`echo $response | jq -r .test_suite_url`
+echo "Test Suite URL: $test_suite_url"
+
+# export the test suite URL as an environment variable
+export PERCY_TEST_SUITE_URL=$test_suite_url