diff --git a/.circleci/config.yml b/.circleci/config.yml
index 2865144efcc..c63f99a1b93 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -175,7 +175,7 @@ jobs:
BUNDLE_PATH: .vendor
macos:
- xcode: 16.1
+ xcode: 16.2
resource_class: macos.m1.medium.gen1
steps:
@@ -191,12 +191,58 @@ jobs:
name: Update rollout if needed
command: ./scripts/deploys/update-android-rollout-if-needed
+ run-e2e-tests:
+ parallelism: 4
+
+ environment:
+ BUNDLE_PATH: .vendor # path to install gems and use for caching
+
+ macos:
+ xcode: 16.2
+ resource_class: m2pro.medium
+
+ steps:
+ - attach_workspace:
+ at: ../workspace
+ - macos/preboot-simulator:
+ version: "18.2"
+ platform: "iOS"
+ device: "iPhone 16 Pro"
+ - run:
+ name: Install maestro
+ command: curl -Ls "https://get.maestro.mobile.dev" | bash
+ - run:
+ name: Install idb
+ command: brew tap facebook/fb && brew install idb-companion
+ - run:
+ name: Clear project dir
+ command: |
+ rm -rf /Users/distiller/project/*
+ - checkout
+ - setup-awscli
+ - run:
+ name: Set up maestro env
+ command: ./scripts/setup/setup-maestro-env.sh
+ - run:
+ name: Download app zip
+ command: aws s3 cp s3://artsy-citadel/eigen/builds/ios/Artsy-latest.zip ./Artsy.zip
+ - run:
+ name: Unzip the app
+ command: unzip Artsy.zip
+ - run:
+ name: Install app in booted sims
+ command: ./scripts/utils/install_app_in_booted_sims.sh
+ - run:
+ name: Run Maestro Tests
+ no_output_timeout: 25m
+ command: ./scripts/utils/run_maestro_shard.sh
+
deploy-nightly-beta:
environment:
BUNDLE_PATH: .vendor
macos:
- xcode: 16.1
+ xcode: 16.2
resource_class: macos.m1.medium.gen1
steps:
@@ -217,7 +263,7 @@ jobs:
BUNDLE_PATH: .vendor
macos:
- xcode: 16.1
+ xcode: 16.2
resource_class: macos.m1.medium.gen1
steps:
@@ -309,7 +355,7 @@ jobs:
BUNDLE_PATH: .vendor # path to install gems and use for caching
macos:
- xcode: 16.1
+ xcode: 16.2
resource_class: macos.m1.medium.gen1
steps:
@@ -383,7 +429,7 @@ jobs:
BUNDLE_PATH: .vendor # path to install gems and use for caching
macos:
- xcode: 16.1
+ xcode: 16.2
resource_class: macos.m1.medium.gen1
steps:
@@ -423,7 +469,7 @@ jobs:
- install-gems
- install-cocoapods
- macos/preboot-simulator:
- version: "18.1"
+ version: "18.2"
platform: "iOS"
device: "iPhone 16 Pro"
- build-app-ios
@@ -518,8 +564,12 @@ workflows:
only:
- main
jobs:
+ - build-test-app-ios
- deploy-nightly-beta
- run-nightly-tasks
+ - run-e2e-tests:
+ requires:
+ - build-test-app-ios
flag-check:
triggers:
@@ -600,3 +650,11 @@ workflows:
requires:
- build-test-app-ios
- build-test-app-android
+
+ - run-e2e-tests:
+ filters:
+ branches:
+ only:
+ - beta-ios
+ requires:
+ - build-test-app-ios
diff --git a/.gitignore b/.gitignore
index f991d81b75e..66817f83912 100644
--- a/.gitignore
+++ b/.gitignore
@@ -151,6 +151,9 @@ tsconfig.tsbuildinfo
# Temporary files created by Metro to check the health of the file watcher
.metro-health-check*
+# maestro test artifacts
+.maestro/*
+
#########################################################
# Keep this rule last.
# Keep these, so we can keep the folders these appear in.
diff --git a/app.json b/app.json
index 414cff43fc8..0e256659271 100644
--- a/app.json
+++ b/app.json
@@ -1,6 +1,6 @@
{
"appName": "eigen",
- "version": "8.74.0",
+ "version": "8.75.0",
"isAndroidBeta": false,
"slug": "eigen",
"expo": {
diff --git a/e2e/flows/onboarding/onboardingQuiz.js b/e2e/broken_flows/onboardingQuiz.js
similarity index 100%
rename from e2e/flows/onboarding/onboardingQuiz.js
rename to e2e/broken_flows/onboardingQuiz.js
diff --git a/e2e/flows/onboarding/onboardingQuiz.yml b/e2e/broken_flows/onboardingQuiz.yml
similarity index 100%
rename from e2e/flows/onboarding/onboardingQuiz.yml
rename to e2e/broken_flows/onboardingQuiz.yml
diff --git a/e2e/config.yml b/e2e/config.yml
index 26021c094eb..86e2329dfbe 100644
--- a/e2e/config.yml
+++ b/e2e/config.yml
@@ -1,9 +1,4 @@
appId: ${MAESTRO_APP_ID}
---
-- runFlow:
- file: flows/onboarding/login.yml
-- runFlow:
- file: flows/onboarding/signupWithoutOnboarding.yml
-# This flow is broken because of performance issues and hangs of the Art Taste Quiz Screen
-# - runFlow:
-# file: flows/onboarding/onboardingQuiz.yml
+flows:
+ - "flows/*"
diff --git a/e2e/flows/deeplinks.yml b/e2e/flows/deeplinks.yml
new file mode 100644
index 00000000000..601785a866e
--- /dev/null
+++ b/e2e/flows/deeplinks.yml
@@ -0,0 +1,23 @@
+appId: ${MAESTRO_APP_ID}
+---
+- launchApp:
+ clearState: false
+ arguments:
+ email: ${MAESTRO_TEST_EMAIL}
+ password: ${MAESTRO_TEST_PASSWORD}
+- assertVisible: "New Works for You"
+- killApp
+- openLink:
+ link: https://www.artsy.net/artist/kaws
+ autoVerify: false
+- assertVisible: "Kaws"
+- killApp
+- openLink:
+ link: https://click.artsy.net/f/a/epPWiX0dJaHHMiXAnSrzSg~~/AAQRxQA~/RgRj95QSP0SQaHR0cHM6Ly93d3cuYXJ0c3kubmV0L2NvbGxlY3Rpb24vYXVjdGlvbi1oaWdobGlnaHRzP3V0bV9zb3VyY2U9YnJhemUmdXRtX21lZGl1bT1lbWFpbCZ1dG1fY2FtcGFpZ249bnRmJnV0bV90ZXJtPTYyMTRmYjlkZWI0ZWZkN2RiNjBjZTcwNDk1NWU4NDQ1VwNzcGNCCmIFEg8VYlEJKGBSEmJyaWJlY2sxQGdtYWlsLmNvbVgEAAAAfw~~
+ autoVerify: false
+- assertVisible: "Highlights at Auction This Week"
+- killApp
+- openLink:
+ link: "https://email-link.artsy.net/ls/click?upn=u001.XPbk8dXjc6dwd18Yq2uPM3eTWB4YbDUmeJTAXXTF513sTSKEFRIPxrFgGj8gd2LN9AcRIza5p2-2Fi0huqjv6GWgdnpwA-2FRyTEIYA-2BT1-2F0josxHnYTzJpKeJEmkfzDQlMEY9y6FknqDddY-2BKmm5qtTrTIF2hxySbdUboaYf5ofK-2Bj-2FgSolzBiLvvzai-2FmvjWbSpNpJHWATexQixwLakesmmZUBoRoLkCnxs59pBxJPbB3JZyP13cv77lbfn1WzJ86fiW-2B5MQcd2qss7wLGBI-2BZuy-2FxDkbFuDzpfYPDlaUs1-2BuyO9rqleUCwXPEuyWXZ3aGJrh-_dWIRBTTKlL2IhQqV5a1Cy38Sd4xylePmkTfQkXpdWOLZbG0vcWuzG-2BSSjZ7xaSUgPR0lxFBD0Zz4mpyMqiSRd7GblcbPN8eH0NYfUK6pmADWc2UoFXyJdeq-2F-2BOyOeWlS7O-2FOrHgB9-2BOGtV1nkIFPq97vJTNmWtBMDOt77SsursARR1LHG7CnzBEedV5yll40aEh9LTjA47-2BfpvonTwdxvYfd6ZWsZN2uRczvCs34ipSKUClLSzq0xCn8IjAixssMm2IreBk6LmNzHPC1YLqmZ77pv0qTxX095mxh8mk7f9rJJ8Tg1fyfRFriXGsQqgHlY-2BqTf2aAU6LS9NVXirqdp20-2BHXhzUZbaCnkQNASABts91iKoZaDeV4xnYuuo3L-2F8"
+ autoVerify: false
+- assertVisible: "The artwork you were looking for isn't available."
diff --git a/e2e/flows/login.yml b/e2e/flows/login.yml
new file mode 100644
index 00000000000..764be5370c4
--- /dev/null
+++ b/e2e/flows/login.yml
@@ -0,0 +1,13 @@
+appId: ${MAESTRO_APP_ID}
+---
+- launchApp:
+ clearState: false
+ arguments:
+ shouldSignOut: "true"
+- assertVisible: "Sign up or log in"
+- tapOn: "Email Input"
+- inputText: ${MAESTRO_TEST_EMAIL}
+- tapOn: "Continue.*"
+- inputText: ${MAESTRO_TEST_PASSWORD}
+- tapOn: "Continue.*"
+- assertVisible: "New Works for You"
diff --git a/e2e/flows/onboarding/login.yml b/e2e/flows/onboarding/login.yml
deleted file mode 100644
index 7dc9535ca5d..00000000000
--- a/e2e/flows/onboarding/login.yml
+++ /dev/null
@@ -1,31 +0,0 @@
-appId: ${MAESTRO_APP_ID}
----
-- launchApp:
- clearState: true
- clearKeychain: true
-- tapOn: "Log in.*"
-- tapOn: "Continue with Email.*"
-- inputText: ${MAESTRO_TEST_EMAIL}
-- runFlow:
- when:
- platform: iOS
- commands:
- - tapOn: "Password show password button"
-- runFlow:
- when:
- platform: Android
- commands:
- - tapOn: "password"
-- inputText: ${MAESTRO_TEST_PASSWORD}
-- tapOn:
- id: "loginButton"
-# Related to the issue https://github.com/mobile-dev-inc/maestro/issues/1227
-# it avoids getting stuck on iOS modal asking to store the password
-- runFlow:
- when:
- platform: iOS
- visible: "Would you like to save this password in your Keychain to use with apps and websites?"
- commands:
- - tapOn:
- id: "Home Grabber"
-- assertVisible: "New Works for You"
diff --git a/e2e/flows/onboarding/signup.yml b/e2e/flows/onboarding/signup.yml
deleted file mode 100644
index b9955ac828d..00000000000
--- a/e2e/flows/onboarding/signup.yml
+++ /dev/null
@@ -1,25 +0,0 @@
-appId: ${MAESTRO_APP_ID}
----
-- launchApp:
- clearState: true
- clearKeychain: true
-- tapOn: "Sign up.*"
-- tapOn: "Continue with Email.*"
-- runScript:
- file: signup.js
-- inputText: ${output.signup.email}
-- tapOn: "Next.*"
-- inputText: ${output.signup.password}
-- tapOn: "Next.*"
-- inputText: "Test McTest"
-- runFlow:
- when:
- platform: iOS
- commands:
- - tapOn: "checkbox of consent By checking this box, you consent to our Terms of Use, Privacy Policy, and Conditions of Sale."
-- runFlow:
- when:
- platform: Android
- commands:
- - tapOn: "Accept terms and privacy policy, Check this element to accept Artsy's terms and privacy policy"
-- tapOn: "Next.*"
diff --git a/e2e/flows/onboarding/signup.js b/e2e/flows/signup.js
similarity index 100%
rename from e2e/flows/onboarding/signup.js
rename to e2e/flows/signup.js
diff --git a/e2e/flows/signup.yml b/e2e/flows/signup.yml
new file mode 100644
index 00000000000..82e1a71bbe4
--- /dev/null
+++ b/e2e/flows/signup.yml
@@ -0,0 +1,21 @@
+appId: ${MAESTRO_APP_ID}
+---
+- launchApp:
+ clearState: false
+ arguments:
+ shouldSignOut: "true"
+- runScript:
+ file: signup.js
+- assertVisible: "Sign up or log in"
+- tapOn: "Email Input"
+- inputText: ${output.signup.email}
+- tapOn: "Continue.*"
+- assertVisible: "Welcome to Artsy"
+- inputText: ${output.signup.password}
+- tapOn: "Continue.*"
+- inputText: "Test McTest"
+- runFlow:
+ commands:
+ - tapOn:
+ point: 15%,36%
+- tapOn: "Continue.*"
diff --git a/e2e/flows/onboarding/signupWithoutOnboarding.yml b/e2e/flows/signupWithoutOnboarding.yml
similarity index 71%
rename from e2e/flows/onboarding/signupWithoutOnboarding.yml
rename to e2e/flows/signupWithoutOnboarding.yml
index dd4f22bdf48..9ce5e6beb0e 100644
--- a/e2e/flows/onboarding/signupWithoutOnboarding.yml
+++ b/e2e/flows/signupWithoutOnboarding.yml
@@ -2,5 +2,6 @@ appId: ${MAESTRO_APP_ID}
---
- runFlow:
file: signup.yml
+- assertVisible: "Ready to find art you love?"
- tapOn: "Skip.*"
- assertVisible: "New Works for You"
diff --git a/fastlane/Fastfile b/fastlane/Fastfile
index 2f5dd10d59a..68c4ab6d070 100644
--- a/fastlane/Fastfile
+++ b/fastlane/Fastfile
@@ -47,6 +47,7 @@ GIT_REMOTE_ORIGIN_URL = `git config --get remote.origin.url`.chomp
S3_PATH = 's3://artsy-citadel/eigen'
S3_ANDROID_BUILDS_PATH = S3_PATH + '/builds/android/'
+S3_IOS_BUILDS_PATH = S3_PATH + '/builds/ios/'
import 'utility_fastlane.rb'
import 'sentry_fastlane.rb'
@@ -361,12 +362,6 @@ lane :create_android_apk do
)
end
-lane :s3_upload_android_build do |options|
- app_version = options[:app_version]
- app_path = options[:app_path]
- sh('aws s3 cp ' + app_path + ' ' + S3_ANDROID_BUILDS_PATH + app_version.to_s + '.aab')
-end
-
lane :select_android_build do |options|
select_message = options[:select_message] || 'release'
skip_download = options[:skip_download] || false
diff --git a/fastlane/utility_fastlane.rb b/fastlane/utility_fastlane.rb
index 9d662bab1a3..2c4f3192851 100644
--- a/fastlane/utility_fastlane.rb
+++ b/fastlane/utility_fastlane.rb
@@ -1,3 +1,5 @@
+require "pathname"
+
# Utility functions
desc "Updates the version string in app.json"
@@ -70,6 +72,49 @@
)
end
+# Build and upload iOS and Android builds to S3
+
+lane :s3_upload_ios_build do |options|
+ UI.message("Uploading iOS build to S3...")
+
+ app_version = options[:app_version]
+ archive_root = options[:archive_root] || "../archives"
+ app_name = options[:app_name] || "Artsy"
+
+ # Find latest archive
+ pattern = File.join(archive_root, "#{app_name}*.xcarchive")
+ matching_archives = Dir.glob(pattern)
+
+ UI.user_error!("No .xcarchive found matching pattern #{pattern}") unless matching_archives.any?
+
+ latest_archive = matching_archives.max_by { |f| File.mtime(f) }
+ app_path = File.join(latest_archive, "Products/Applications/#{app_name}.app")
+ UI.user_error!("App not found at #{app_path}") unless File.exist?(app_path)
+
+ # Create zip
+ zip_name = "#{app_name}-#{app_version}.zip"
+ zip_path = File.join(archive_root, zip_name)
+ latest_zip_path = File.join(archive_root, "#{app_name}-latest.zip")
+ app_dir = File.dirname(app_path)
+ app_folder = File.basename(app_path)
+ zip_path_absolute = Pathname.new(zip_path).realpath.to_s rescue Pathname.new(zip_path).expand_path.to_s
+
+ sh("cd '#{app_dir}' && zip -r '#{zip_path_absolute}' '#{app_folder}'")
+ FileUtils.cp(zip_path, latest_zip_path)
+
+ # Upload both versioned and "latest"
+ sh("aws s3 cp #{zip_path} #{S3_IOS_BUILDS_PATH}#{zip_name}")
+ sh("aws s3 cp #{latest_zip_path} #{S3_IOS_BUILDS_PATH}#{app_name}-latest.zip")
+
+ UI.success("✅ Uploaded #{zip_name} and latest to S3")
+end
+
+lane :s3_upload_android_build do |options|
+ app_version = options[:app_version]
+ app_path = options[:app_path]
+ sh('aws s3 cp ' + app_path + ' ' + S3_ANDROID_BUILDS_PATH + app_version.to_s + '.aab')
+end
+
lane :tag_and_push do |options|
# Do a tag, we use a http git remote so we can have push access
# as the default remote for circle is read-only
diff --git a/ios/Artsy/Networking/API_Modules/ArtsyAPI+DeviceTokens.m b/ios/Artsy/Networking/API_Modules/ArtsyAPI+DeviceTokens.m
index 9ea54b05ef0..b9b3cda3069 100644
--- a/ios/Artsy/Networking/API_Modules/ArtsyAPI+DeviceTokens.m
+++ b/ios/Artsy/Networking/API_Modules/ArtsyAPI+DeviceTokens.m
@@ -13,6 +13,13 @@ + (AFHTTPRequestOperation *)setAPNTokenForCurrentDevice:(NSString *)token succes
if (token && name) {
NSURLRequest *request = [ARRouter newSetDeviceAPNTokenRequest:token forDevice:name];
+
+ // TODO: Is it a problem that this can be nil?
+ // Should we fail loudly? Wait for the httpClient to be ready?
+ if (!request) {
+ return nil;
+ }
+
return [ArtsyAPI performRequest:request success:success failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
if (failure) {
failure(error);
@@ -31,6 +38,11 @@ + (AFHTTPRequestOperation *)deleteAPNTokenForCurrentDeviceWithCompletion:(void (
return nil;
}
NSURLRequest *request = [ARRouter newDeleteDeviceRequest:token];
+
+ if (!request) {
+ completion();
+ return nil;
+ }
return [ArtsyAPI performRequest:request success:^ (id _) {
completion();
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
diff --git a/ios/ArtsyTests/Supporting_Files/ARTestHelper.m b/ios/ArtsyTests/Supporting_Files/ARTestHelper.m
index f6445edc6f4..6be9aa1a068 100644
--- a/ios/ArtsyTests/Supporting_Files/ARTestHelper.m
+++ b/ios/ArtsyTests/Supporting_Files/ARTestHelper.m
@@ -16,8 +16,8 @@ - (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:
{
NSOperatingSystemVersion version = [NSProcessInfo processInfo].operatingSystemVersion;
- NSAssert(version.majorVersion == 18 && version.minorVersion == 1,
- @"The tests should be run on iOS 18.1, not %ld.%ld", version.majorVersion, version.minorVersion);
+ NSAssert(version.majorVersion == 18 && version.minorVersion == 2,
+ @"The tests should be run on iOS 18.2, not %ld.%ld", version.majorVersion, version.minorVersion);
CGSize nativeResolution = [UIScreen mainScreen].nativeBounds.size;
NSAssert([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone && CGSizeEqualToSize(nativeResolution, CGSizeMake(1206, 2622)),
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index 10b1217743d..2dfc43dff21 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -1668,6 +1668,8 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- Yoga
+ - react-native-launch-arguments (4.0.4):
+ - React
- react-native-netinfo (11.3.2):
- React-Core
- react-native-pager-view (6.5.0):
@@ -2443,6 +2445,7 @@ DEPENDENCIES:
- react-native-get-random-values (from `../node_modules/react-native-get-random-values`)
- react-native-in-app-review (from `../node_modules/react-native-in-app-review`)
- react-native-keys (from `../node_modules/react-native-keys`)
+ - react-native-launch-arguments (from `../node_modules/react-native-launch-arguments`)
- "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)"
- react-native-pager-view (from `../node_modules/react-native-pager-view`)
- react-native-render-html (from `../node_modules/react-native-render-html`)
@@ -2737,6 +2740,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-in-app-review"
react-native-keys:
:path: "../node_modules/react-native-keys"
+ react-native-launch-arguments:
+ :path: "../node_modules/react-native-launch-arguments"
react-native-netinfo:
:path: "../node_modules/@react-native-community/netinfo"
react-native-pager-view:
@@ -3002,6 +3007,7 @@ SPEC CHECKSUMS:
react-native-get-random-values: d16467cf726c618e9c7a8c3c39c31faa2244bbba
react-native-in-app-review: b3d1eed3d1596ebf6539804778272c4c65e4a400
react-native-keys: b8628f6db75d113f89bae4a69422e58b0bbcd373
+ react-native-launch-arguments: 8ad8efa525a4c7cb72b3f69f05abfcba86f65dea
react-native-netinfo: ce102083db558237dac20cf64172ef569ebe2dd9
react-native-pager-view: c8b6ddfc7747d272ad953e632f1296e98ad59e28
react-native-render-html: 5afc4751f1a98621b3009432ef84c47019dcb2bd
diff --git a/package.json b/package.json
index 9a0d20ad68e..14f2c854b06 100644
--- a/package.json
+++ b/package.json
@@ -59,6 +59,8 @@
"setup:artsy": "./scripts/setup/setup-env-for-artsy",
"setup:artsy:update!": "scripts/setup/update-env-for-artsy",
"setup:oss": "./scripts/setup/setup-env-for-oss",
+ "setup:maestro": "./scripts/setup/setup-env-for-maestro",
+ "setup:maestro:update!": "scripts/setup/update-env-for-maestro",
"setup:releases": "./scripts/setup/setup-env-for-artsy && ./scripts/setup/setup-env-for-releases",
"setup:releases:update!": "scripts/setup/update-env-for-releases",
"start": "concurrently 'yarn relay:watch' 'react-native start'",
@@ -167,6 +169,7 @@
"react-native-in-app-review": "4.3.3",
"react-native-keychain": "9.2.2",
"react-native-keys": "0.7.11",
+ "react-native-launch-arguments": "4.0.4",
"react-native-linear-gradient": "2.8.3",
"react-native-localize": "2.1.3",
"react-native-pager-view": "6.5.0",
diff --git a/scripts/ci/build-for-tests-ios b/scripts/ci/build-for-tests-ios
index d91bf82b204..05a213fa394 100755
--- a/scripts/ci/build-for-tests-ios
+++ b/scripts/ci/build-for-tests-ios
@@ -1,6 +1,8 @@
#!/usr/bin/env bash
set -euxo pipefail
-xcodebuild -workspace "$WORKSPACE" -scheme "$SCHEME" -configuration Debug -sdk iphonesimulator build -destination platform="$DEVICE_HOST_PLAT",OS="$DEVICE_HOST_OS",name="$DEVICE_HOST_NAME" -derivedDataPath "$DERIVED_DATA_PATH" GCC_PREPROCESSOR_DEFINITIONS='$(inherited)' |
+xcodebuild -workspace "$WORKSPACE" -scheme "$SCHEME" -configuration Debug -sdk iphonesimulator build -destination platform="$DEVICE_HOST_PLAT",OS="$DEVICE_HOST_OS",name="$DEVICE_HOST_NAME" -derivedDataPath "$DERIVED_DATA_PATH" ONLY_ACTIVE_ARCH=YES GCC_PREPROCESSOR_DEFINITIONS='$(inherited)' |
tee ./xcode_build_raw.log |
bundle exec xcpretty -c
+
+./scripts/ci/upload_sim_app
\ No newline at end of file
diff --git a/scripts/ci/ci-setup-export-vars b/scripts/ci/ci-setup-export-vars
index e6e4bb6766d..602c5ff5f20 100755
--- a/scripts/ci/ci-setup-export-vars
+++ b/scripts/ci/ci-setup-export-vars
@@ -10,7 +10,7 @@ export LOCAL_BRANCH
export WORKSPACE="ios/Artsy.xcworkspace"
export SCHEME="Artsy"
export DEVICE_HOST_PLAT="iOS Simulator"
-export DEVICE_HOST_OS="18.1"
+export DEVICE_HOST_OS="18.2"
export DEVICE_HOST_NAME="iPhone 16 Pro"
export DERIVED_DATA_PATH="derived_data"
export CONFIGURATION="Release"
diff --git a/scripts/ci/upload_sim_app b/scripts/ci/upload_sim_app
new file mode 100755
index 00000000000..01c40dc187c
--- /dev/null
+++ b/scripts/ci/upload_sim_app
@@ -0,0 +1,26 @@
+#!/usr/bin/env bash
+set -euxo pipefail
+
+APP_NAME="Artsy"
+DERIVED_DATA_PATH="${DERIVED_DATA_PATH:-derived_data}"
+S3_DEST="s3://artsy-citadel/eigen/builds/ios/${APP_NAME}-latest.zip"
+
+# Find the .app bundle
+APP_PATH=$(find "$DERIVED_DATA_PATH/Build/Products/Debug-iphonesimulator" -name "${APP_NAME}.app" -type d | head -n1)
+
+if [ -z "$APP_PATH" ]; then
+ echo "❌ .app not found!" >&2
+ exit 1
+fi
+
+echo "✅ Found app at: $APP_PATH"
+
+# Zip the .app bundle
+ZIP_NAME="${APP_NAME}-latest.zip"
+cd "$(dirname "$APP_PATH")"
+zip -r "$ZIP_NAME" "$(basename "$APP_PATH")"
+
+# Upload to S3
+aws s3 cp "$ZIP_NAME" "$S3_DEST"
+
+echo "✅ Uploaded $ZIP_NAME to $S3_DEST"
diff --git a/scripts/setup/setup-env-for-maestro b/scripts/setup/setup-env-for-maestro
new file mode 100755
index 00000000000..0c68823b07c
--- /dev/null
+++ b/scripts/setup/setup-env-for-maestro
@@ -0,0 +1,4 @@
+#!/usr/bin/env bash
+set -euxo pipefail
+
+aws s3 cp s3://artsy-citadel/eigen/.env.maestro .env.maestro
diff --git a/scripts/setup/update-env-for-maestro b/scripts/setup/update-env-for-maestro
new file mode 100755
index 00000000000..68f5f99ce77
--- /dev/null
+++ b/scripts/setup/update-env-for-maestro
@@ -0,0 +1,15 @@
+#!/usr/bin/env bash
+set -euxo pipefail
+
+
+read -p "Are you sure you want to update the env vars in S3? " -n 1 -r
+
+if [[ $REPLY =~ ^[Yy]$ ]]
+then
+ aws s3 cp .env.maestro s3://artsy-citadel/eigen/.env.maestro
+
+ RED='\033[0;31m'
+ RST='\033[0m'
+
+ printf "${RED}Don't forget to update on 1Password and CircleCI too!${RST}\n"
+fi
diff --git a/scripts/utils/install_app_in_booted_sims.sh b/scripts/utils/install_app_in_booted_sims.sh
new file mode 100755
index 00000000000..2013749c769
--- /dev/null
+++ b/scripts/utils/install_app_in_booted_sims.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+# Path to your .app file
+APP_PATH="./Artsy.app"
+
+# Get a list of all booted simulator UUIDs
+BOOTED_SIMULATORS=$(xcrun simctl list devices booted | grep -oE "[A-F0-9-]{36}")
+
+# Install the .app file on each booted simulator
+for SIMULATOR in $BOOTED_SIMULATORS; do
+ echo "Installing $APP_PATH on simulator $SIMULATOR..."
+ xcrun simctl install $SIMULATOR "$APP_PATH"
+done
+
+echo "Installation completed on all booted simulators!"
\ No newline at end of file
diff --git a/scripts/utils/run_maestro_shard.sh b/scripts/utils/run_maestro_shard.sh
new file mode 100755
index 00000000000..1626927e3f9
--- /dev/null
+++ b/scripts/utils/run_maestro_shard.sh
@@ -0,0 +1,16 @@
+#!/bin/bash
+
+TEST_FILES=$(find e2e/flows -name "*.yml" | sort | awk "NR % ${CIRCLE_NODE_TOTAL} == ${CIRCLE_NODE_INDEX}")
+
+EXIT_CODE=0
+
+for TEST_FILE in $TEST_FILES; do
+ echo "Running test: $TEST_FILE"
+ maestro test "$TEST_FILE" @.env.maestro
+ if [ $? -ne 0 ]; then
+ EXIT_CODE=1
+ fi
+done
+
+echo "Final Exit Code: $EXIT_CODE"
+exit $EXIT_CODE
\ No newline at end of file
diff --git a/src/app/App.tsx b/src/app/App.tsx
index 59c57164995..aea52d460bf 100644
--- a/src/app/App.tsx
+++ b/src/app/App.tsx
@@ -3,6 +3,7 @@ import * as Sentry from "@sentry/react-native"
import { Navigation } from "app/Navigation/Navigation"
import { GlobalStore, unsafe__getEnvironment, unsafe_getDevToggle } from "app/store/GlobalStore"
import { DevMenuWrapper } from "app/system/devTools/DevMenu/DevMenuWrapper"
+import { useMaestroInitialization } from "app/system/devTools/useMaestroInitialization"
import { useRageShakeDevMenu } from "app/system/devTools/useRageShakeDevMenu"
import { setupSentry } from "app/system/errorReporting/setupSentry"
import { usePurgeCacheOnAppUpdate } from "app/system/relay/usePurgeCacheOnAppUpdate"
@@ -64,6 +65,7 @@ if (UIManager.setLayoutAnimationEnabledExperimental) {
const Main = () => {
useRageShakeDevMenu()
+ useMaestroInitialization()
useEffect(() => {
const oss = Keys.OSS
diff --git a/src/app/Scenes/Onboarding/Auth2/scenes/LoginPasswordStep.tsx b/src/app/Scenes/Onboarding/Auth2/scenes/LoginPasswordStep.tsx
index 2597e94c553..98429812169 100644
--- a/src/app/Scenes/Onboarding/Auth2/scenes/LoginPasswordStep.tsx
+++ b/src/app/Scenes/Onboarding/Auth2/scenes/LoginPasswordStep.tsx
@@ -120,6 +120,7 @@ const LoginPasswordStepForm: React.FC = () => {
{
{
+ const isLoggedIn = GlobalStore.useAppState((state) => !!state.auth.userAccessToken)
+ const isHydrated = GlobalStore.useAppState((state) => state.sessionState.isHydrated)
+
+ useEffect(() => {
+ if (!ArtsyNativeModule.isBetaOrDev || !isHydrated) {
+ return
+ }
+
+ const args = LaunchArguments.value()
+ const email = args.email
+ const password = args.password
+ const shouldSignOut = args.shouldSignOut
+
+ if (email && password) {
+ GlobalStore.actions.auth.signIn({
+ oauthProvider: "email",
+ oauthMode: "email",
+ email,
+ password,
+ })
+ } else if (shouldSignOut && isLoggedIn) {
+ GlobalStore.actions.auth.signOut()
+ }
+ }, [isHydrated])
+}
diff --git a/yarn.lock b/yarn.lock
index b15d5b77760..ecf2a89b79e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2005,7 +2005,7 @@
"@babel/parser" "^7.27.0"
"@babel/types" "^7.27.0"
-"@babel/traverse--for-generate-function-map@npm:@babel/traverse@^7.25.3":
+"@babel/traverse--for-generate-function-map@npm:@babel/traverse@^7.25.3", "@babel/traverse@^7.25.3":
version "7.26.4"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.26.4.tgz#ac3a2a84b908dde6d463c3bfa2c5fdc1653574bd"
integrity sha512-fH+b7Y4p3yqvApJALCPJcwb0/XaOSgtK4pzV6WVjPR5GLFQBRI7pfoX2V2iM48NXvX07NUxxm1Vw98YjqTcU5w==
@@ -2047,19 +2047,6 @@
debug "^4.3.1"
globals "^11.1.0"
-"@babel/traverse@^7.25.3":
- version "7.26.4"
- resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.26.4.tgz#ac3a2a84b908dde6d463c3bfa2c5fdc1653574bd"
- integrity sha512-fH+b7Y4p3yqvApJALCPJcwb0/XaOSgtK4pzV6WVjPR5GLFQBRI7pfoX2V2iM48NXvX07NUxxm1Vw98YjqTcU5w==
- dependencies:
- "@babel/code-frame" "^7.26.2"
- "@babel/generator" "^7.26.3"
- "@babel/parser" "^7.26.3"
- "@babel/template" "^7.25.9"
- "@babel/types" "^7.26.3"
- debug "^4.3.1"
- globals "^11.1.0"
-
"@babel/traverse@^7.25.7":
version "7.25.7"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.7.tgz#83e367619be1cab8e4f2892ef30ba04c26a40fa8"
@@ -14389,6 +14376,11 @@ react-native-keys@0.7.11:
walk-sync "^3.0.0"
xml2js "^0.6.2"
+react-native-launch-arguments@4.0.4:
+ version "4.0.4"
+ resolved "https://registry.yarnpkg.com/react-native-launch-arguments/-/react-native-launch-arguments-4.0.4.tgz#5cdf12265b50bf98ad05c87f849da99e07ef5500"
+ integrity sha512-cB7Sy9m9MX5MvNJliSHEMFWRScSbr95gFuGWnaINBoIK9sP8hloKqhn0vKUHrMQGmNsC1PcpyeiniBqTiU9d5g==
+
react-native-linear-gradient@2.8.3:
version "2.8.3"
resolved "https://registry.yarnpkg.com/react-native-linear-gradient/-/react-native-linear-gradient-2.8.3.tgz#9a116649f86d74747304ee13db325e20b21e564f"
@@ -15888,16 +15880,7 @@ string-natural-compare@^3.0.1:
resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4"
integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==
-"string-width-cjs@npm:string-width@^4.2.0":
- version "4.2.3"
- resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
- integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
- dependencies:
- emoji-regex "^8.0.0"
- is-fullwidth-code-point "^3.0.0"
- strip-ansi "^6.0.1"
-
-string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
+"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -16011,7 +15994,7 @@ stringify-entities@^3.1.0:
character-entities-legacy "^1.0.0"
xtend "^4.0.0"
-"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
+"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -16025,13 +16008,6 @@ strip-ansi@^5.0.0, strip-ansi@^5.2.0:
dependencies:
ansi-regex "^4.1.0"
-strip-ansi@^6.0.0, strip-ansi@^6.0.1:
- version "6.0.1"
- resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
- integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
- dependencies:
- ansi-regex "^5.0.1"
-
strip-ansi@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2"
@@ -17569,7 +17545,7 @@ word-wrap@^1.2.3, word-wrap@~1.2.3:
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.4.tgz#cb4b50ec9aca570abd1f52f33cd45b6c61739a9f"
integrity sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==
-"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
+"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@@ -17587,15 +17563,6 @@ wrap-ansi@^6.2.0:
string-width "^4.1.0"
strip-ansi "^6.0.0"
-wrap-ansi@^7.0.0:
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
- integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
- dependencies:
- ansi-styles "^4.0.0"
- string-width "^4.1.0"
- strip-ansi "^6.0.0"
-
wrap-ansi@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"