diff --git a/.github/workflows/android-espresso.yml b/.github/workflows/android-espresso.yml
new file mode 100644
index 0000000000..c1ecedda38
--- /dev/null
+++ b/.github/workflows/android-espresso.yml
@@ -0,0 +1,208 @@
+name: Android Espresso E2E
+
+on:
+ workflow_dispatch:
+ push:
+ branches: [master, develop, testlio/ui-tests]
+ pull_request:
+ branches: [master, develop, testlio/ui-tests]
+
+concurrency:
+ group: e2e-android-espresso-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ android-espresso-tests:
+ name: Android Espresso UI Tests
+ runs-on: ubuntu-latest
+ timeout-minutes: 120
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Free disk space
+ run: |
+ echo "=== Disk before cleanup ==="
+ df -h
+ sudo rm -rf /usr/share/dotnet
+ sudo rm -rf /usr/local/lib/android/sdk/ndk/2[0-6]*
+ sudo rm -rf /opt/ghc
+ sudo rm -rf /usr/local/share/boost
+ sudo rm -rf "$AGENT_TOOLSDIRECTORY"
+ echo "=== Disk after cleanup ==="
+ df -h
+
+ - name: Setup Node
+ uses: actions/setup-node@v4
+ with:
+ node-version-file: '.node-version'
+ cache: 'yarn'
+
+ - name: Setup Java
+ uses: actions/setup-java@v4
+ with:
+ distribution: 'temurin'
+ java-version: '17'
+
+ - name: Install dependencies
+ run: yarn install --frozen-lockfile
+
+ - name: Create env file
+ env:
+ ENV_FILE: ${{ secrets.ENV_DEVELOPMENT }}
+ run: echo "$ENV_FILE" > .env.development
+
+ - name: Create Sentry properties
+ run: |
+ echo "${{ secrets.SENTRY_PROPERTIES }}" > sentry.properties
+ echo "${{ secrets.SENTRY_PROPERTIES }}" > android/sentry.properties
+
+ - name: Set native config values
+ run: yarn set:dev
+
+ - name: Cache Gradle
+ uses: actions/cache@v4
+ with:
+ path: |
+ ~/.gradle/caches
+ ~/.gradle/wrapper
+ key: gradle-${{ hashFiles('android/**/*.gradle*', 'android/gradle/wrapper/gradle-wrapper.properties') }}
+ restore-keys: gradle-
+
+ - name: Enable KVM for emulator
+ run: |
+ echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
+ sudo udevadm control --reload-rules
+ sudo udevadm trigger --name-match=kvm
+
+ - name: Start Metro bundler
+ run: |
+ npx react-native start &
+ echo $! > /tmp/metro.pid
+ npx wait-on http://localhost:8081/status --timeout 60000
+
+ - name: Build debug APK and test APK
+ run: |
+ cd android
+ ./gradlew assembleDebug assembleDebugAndroidTest \
+ -PreactNativeArchitectures=x86_64 \
+ --no-daemon
+
+ - name: Start Android emulator and run Espresso tests
+ uses: reactivecircus/android-emulator-runner@v2
+ timeout-minutes: 75
+ with:
+ api-level: 34
+ arch: x86_64
+ profile: pixel_6
+ avd-name: e2e-emulator
+ emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -memory 4096
+ disable-animations: true
+ script: |
+ adb logcat -c
+ adb logcat > /home/runner/work/_temp/device-logcat.txt &
+ echo $! > /tmp/logcat.pid
+ adb reverse tcp:8081 tcp:8081
+ bash scripts/run-tests.sh
+ kill $(cat /tmp/logcat.pid) || true
+
+ - name: Stop Metro bundler
+ if: always()
+ run: kill $(cat /tmp/metro.pid) || true
+
+ - name: Copy Allure results to workspace
+ if: always()
+ run: |
+ mkdir -p allure-results
+ cp -r /home/runner/work/_temp/allure-results/. allure-results/ || true
+ find allure-results -type f || echo "nothing"
+
+ - name: Check Allure results were produced
+ if: always()
+ run: |
+ if [ -z "$(find allure-results -name '*.json' 2>/dev/null)" ]; then
+ echo "::warning::No Allure result JSON files found under allure-results/"
+ else
+ echo "Allure results found:"
+ find allure-results -name '*.json'
+ fi
+
+ - name: Upload device logcat
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: device-logcat
+ path: /home/runner/work/_temp/device-logcat.txt
+ retention-days: 7
+
+ - name: Upload raw Espresso test results
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: espresso-android-results
+ path: android/app/build/outputs/androidTest-results/connected/
+ retention-days: 7
+
+ - name: Install Allure CLI
+ if: always()
+ run: |
+ sudo apt-get update -q
+ sudo apt-get install -y -q default-jre-headless
+ curl -fLo allure.tgz https://github.com/allure-framework/allure2/releases/download/2.29.0/allure-2.29.0.tgz
+ tar -xzf allure.tgz
+ sudo mv allure-2.29.0 /opt/allure
+ sudo ln -sf /opt/allure/bin/allure /usr/local/bin/allure
+
+ - name: Zip Allure Results
+ if: always()
+ run: |
+ rm -f allure-results.zip
+ zip -r allure-results.zip allure-results/
+ echo "=== Zip contents ==="
+ unzip -l allure-results.zip | head -30
+
+ - name: Upload Allure Results Artifact
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: allure-results
+ path: allure-results.zip
+ retention-days: 7
+
+ - name: Generate Allure HTML Report
+ if: always()
+ run: allure generate allure-results --clean -o allure-report
+
+ - name: Upload Allure HTML Report
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: allure-html-report
+ path: allure-report
+ retention-days: 7
+
+ - name: Install Testlio CLI
+ if: always()
+ run: npm install -g @testlio/cli
+
+ - name: Upload Results to Testlio
+ if: always()
+ env:
+ RUN_API_TOKEN: ${{ secrets.TESTLIO_RUN_TOKEN }}
+ TESTLIO_DEVICE_ID: 363a5ce7-6cef-436c-8531-6c07195c8fe8
+ run: |
+ testlio create-run \
+ --testConfig testlio-cli/test-config.json \
+ --projectConfig testlio-cli/project-config.json \
+ --externalResults true \
+ --resultProvider local \
+ --automatedDeviceIds "$TESTLIO_DEVICE_ID"
+
+ testlio parse-run-results \
+ --projectConfig testlio-cli/project-config.json \
+ --path allure-results.zip \
+ --automatedDeviceId "$TESTLIO_DEVICE_ID"
+
+ testlio finalize-results \
+ --projectConfig testlio-cli/project-config.json
\ No newline at end of file
diff --git a/.github/workflows/ios-xcuitest.yml b/.github/workflows/ios-xcuitest.yml
new file mode 100644
index 0000000000..9a3075a42f
--- /dev/null
+++ b/.github/workflows/ios-xcuitest.yml
@@ -0,0 +1,215 @@
+name: iOS XCUITest
+
+on:
+ workflow_dispatch:
+ push:
+ branches: [master, develop, testlio/ui-tests]
+ pull_request:
+ branches: [master, develop, testlio/ui-tests]
+
+jobs:
+ ios-ui-tests:
+ runs-on: macos-latest
+ timeout-minutes: 180
+
+ steps:
+ # Checkout Repo
+ - name: Checkout Repo
+ uses: actions/checkout@v4
+
+ # Select Xcode
+ - name: Select Xcode 26.0.1
+ run: sudo xcode-select -switch /Applications/Xcode_26.0.1.app
+
+ # Setup Node
+ - name: Setup Node
+ uses: actions/setup-node@v4
+ with:
+ node-version: "20"
+ cache: yarn
+
+ # Install JS dependencies
+ - name: Install JS dependencies
+ run: yarn install --frozen-lockfile
+
+ # Setup Ruby
+ - name: Setup Ruby
+ uses: ruby/setup-ruby@v1
+ with:
+ ruby-version: .ruby-version
+ bundler-cache: false
+
+ # Install Gems
+ - name: Install Gems
+ run: |
+ bundle config set deployment false
+ bundle install
+
+ # Disable Sentry upload
+ - name: Disable Sentry upload
+ run: |
+ echo "SENTRY_DISABLE_AUTO_UPLOAD=true" >> $GITHUB_ENV
+
+ # Install Pods
+ - name: Install Pods
+ run: |
+ cd ios
+ bundle exec pod install
+
+ # Generate React Native Bundle
+ - name: Generate React Native Bundle
+ run: |
+ npx react-native bundle \
+ --entry-file index.js \
+ --platform ios \
+ --dev false \
+ --bundle-output ios/main.jsbundle \
+ --assets-dest ios
+
+ # Boot Simulator
+ - name: Boot Simulator
+ run: |
+ xcrun simctl boot "iPhone 17" || true
+ xcrun simctl bootstatus "iPhone 17" -b
+
+ # Apply production configuration
+ - name: Apply production configuration
+ run: |
+ export NODE_ENV=production
+ yarn set:prod
+
+ # Build for testing
+ - name: Build for testing
+ run: |
+ set -o pipefail
+ xcodebuild \
+ -workspace ios/BitPayApp.xcworkspace \
+ -scheme BitPayApp \
+ -configuration Release \
+ -destination 'platform=iOS Simulator,name=iPhone 17' \
+ -derivedDataPath Build \
+ -only-testing:BitPayAppUITests \
+ build-for-testing
+
+ # Run XCUITest
+ # - name: Run XCUITest
+ # run: |
+ # set -o pipefail
+ # xcodebuild \
+ # -workspace ios/BitPayApp.xcworkspace \
+ # -scheme BitPayApp \
+ # -configuration Release \
+ # -destination 'platform=iOS Simulator,name=iPhone 17' \
+ # -derivedDataPath Build \
+ # -resultBundlePath TestResults.xcresult \
+ # -parallel-testing-enabled NO \
+ # -only-testing:BitPayAppUITests \
+ # test
+
+ - name: Run XCUITest in order
+ run: |
+ set -e
+ set -o pipefail
+
+ rm -rf TestResults.xcresult
+
+ xcodebuild \
+ -workspace ios/BitPayApp.xcworkspace \
+ -scheme BitPayApp \
+ -configuration Release \
+ -destination 'platform=iOS Simulator,name=iPhone 17' \
+ -derivedDataPath Build \
+ -resultBundlePath TestResults.xcresult \
+ -parallel-testing-enabled NO \
+ -only-testing:BitPayAppUITests/Test591_OnboardingCreateWallet \
+ -only-testing:BitPayAppUITests/Test592_ImportWalletRecoveryPhrase \
+ -only-testing:BitPayAppUITests/Test593_BTCConfirmPaymentScreen \
+ -only-testing:BitPayAppUITests/Test594_BasicSwapBTC \
+ -only-testing:BitPayAppUITests/Test595_BasicSellBTC \
+ -only-testing:BitPayAppUITests/Test596_BasicBuyBTC \
+ test
+
+ # Upload XCResult
+ - name: Upload XCUITest Results
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: xcuitest-results
+ path: TestResults.xcresult
+
+ # Install Allure CLI
+ - name: Install Allure CLI
+ if: always()
+ run: brew install allure || true
+
+ # Install allure-xcresult
+ - name: Install allure-xcresult
+ if: always()
+ run: |
+ if ! command -v allure-xcresult &> /dev/null; then
+ curl -L -o allure-xcresult https://github.com/kvld/allure-xcresult/releases/download/v1.5.0/allure-xcresult
+ chmod +x allure-xcresult
+ sudo mv allure-xcresult /usr/local/bin/
+ fi
+
+ # Convert to Allure
+ - name: Convert XCResult to Allure Results
+ if: always()
+ run: |
+ allure-xcresult \
+ --input TestResults.xcresult \
+ --output allure-results
+
+ # Zip Allure
+ - name: Zip Allure Results
+ if: always()
+ run: |
+ zip -r allure-results.zip allure-results
+
+ # Upload Allure Results
+ - name: Upload Allure Results Artifact
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: allure-results
+ path: allure-results.zip
+
+ # Generate HTML
+ - name: Generate Allure HTML Report
+ if: always()
+ run: |
+ allure generate allure-results --clean -o allure-report
+
+ # Upload HTML
+ - name: Upload Allure HTML Report
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: allure-html-report
+ path: allure-report
+
+ # Install Testlio CLI
+ - name: Install Testlio CLI
+ if: always()
+ run: npm install -g @testlio/cli
+
+ # Upload to Testlio
+ - name: Upload Results to Testlio
+ if: always()
+ run: |
+ export RUN_API_TOKEN="${{ secrets.TESTLIO_RUN_TOKEN }}"
+
+ testlio create-run \
+ --testConfig testlio-cli/test-config.json \
+ --projectConfig testlio-cli/project-config.json \
+ --externalResults true \
+ --resultProvider local \
+ --automatedDeviceIds 363a5ce7-6cef-436c-8531-6c07195c8fe8
+
+ testlio parse-run-results \
+ --projectConfig testlio-cli/project-config.json \
+ --path allure-results.zip \
+ --automatedDeviceId 363a5ce7-6cef-436c-8531-6c07195c8fe8
+
+ testlio finalize-results \
+ --projectConfig testlio-cli/project-config.json
diff --git a/README.md b/README.md
index 1aca45d55c..5934ad4a03 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,5 @@
+# tests
+
# BitPay App v2
Welcome to BitPay App v2!
diff --git a/android/app/build.gradle b/android/app/build.gradle
index a483cc4ee5..b0cbb346d0 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -24,6 +24,7 @@ react {
// skip the bundling of the JS bundle and the assets. By default is just 'debug'.
// If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants.
// debuggableVariants = ["liteDebug", "prodDebug"]
+ // debuggableVariants = []
/* Bundling */
// A list containing the node command and its flags. Default is just 'node'.
@@ -54,6 +55,7 @@ react {
/* Autolinking */
autolinkLibrariesWithApp()
+
}
/**
@@ -110,6 +112,7 @@ android {
ndk {
abiFilters "armeabi-v7a", "x86", "x86_64", "arm64-v8a"
}
+ testInstrumentationRunner "io.qameta.allure.android.runners.AllureAndroidJUnitRunner"
}
splits {
abi {
@@ -163,9 +166,30 @@ android {
}
}
+ packagingOptions {
+ pickFirst 'lib/*/libfbjni.so'
+ }
}
dependencies {
+ //Espresso libraries
+ androidTestImplementation 'androidx.test.ext:junit:1.1.5'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
+ androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.5.1'
+ androidTestImplementation 'androidx.test:runner:1.5.2'
+ androidTestImplementation 'androidx.test:rules:1.5.0'
+ androidTestImplementation 'androidx.test:core:1.5.0'
+
+ androidTestImplementation 'org.hamcrest:hamcrest:2.2'
+
+ androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.3.0'
+
+ // Allure reporting
+ androidTestImplementation "io.qameta.allure:allure-kotlin-model:2.4.0"
+ androidTestImplementation "io.qameta.allure:allure-kotlin-commons:2.4.0"
+ androidTestImplementation "io.qameta.allure:allure-kotlin-junit4:2.4.0"
+ androidTestImplementation "io.qameta.allure:allure-kotlin-android:2.4.0"
+
// The version of react-native is set by the React Native Gradle Plugin
implementation("com.facebook.react:react-android")
implementation platform('com.google.firebase:firebase-bom:29.1.0')
@@ -175,6 +199,10 @@ dependencies {
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.0.0")
implementation("androidx.core:core-splashscreen:1.0.0")
+ // Test Orchestrator + TestStorage
+ androidTestUtil "androidx.test:orchestrator:1.5.1"
+ androidTestImplementation "androidx.test.services:test-services:1.5.0"
+
if (hermesEnabled.toBoolean()) {
implementation("com.facebook.react:hermes-android")
} else {
@@ -248,3 +276,4 @@ internal object BundleHash {
// v.sources.java.addGeneratedSourceDirectory(generateHashTask, { it.outputs.files.singleFile })
// }
}
+
diff --git a/android/app/src/androidTest/AndroidManifest.xml b/android/app/src/androidTest/AndroidManifest.xml
new file mode 100644
index 0000000000..343a7b66c4
--- /dev/null
+++ b/android/app/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/androidTest/java/com/bitpay/wallet/base/BaseTest.kt b/android/app/src/androidTest/java/com/bitpay/wallet/base/BaseTest.kt
new file mode 100644
index 0000000000..110afd3c3d
--- /dev/null
+++ b/android/app/src/androidTest/java/com/bitpay/wallet/base/BaseTest.kt
@@ -0,0 +1,135 @@
+package com.bitpay.wallet.base
+
+import android.content.Intent
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.bitpay.wallet.pages.OnboardingPage
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.rules.TestWatcher
+import org.junit.runner.Description
+import com.bitpay.wallet.utils.allureScreenshot
+import org.junit.Rule
+import androidx.test.uiautomator.Until
+import androidx.test.uiautomator.By
+import org.junit.After
+import org.junit.rules.TestName
+
+open class BaseTest {
+
+ @get:Rule
+ val testName = TestName()
+
+ companion object {
+ var skipRelaunch: Boolean = false
+ var skipOnboardingHandling: Boolean = false
+ }
+
+ @get:Rule
+ val screenshotOnFailureRule = object : TestWatcher() {
+ override fun failed(e: Throwable?, description: Description?) {
+ allureScreenshot("Failure - ${description?.methodName}")
+ }
+ }
+
+ private val onboardingPage = OnboardingPage()
+
+// private fun clearAppData() {
+// val instrumentation = InstrumentationRegistry.getInstrumentation()
+// val packageName = instrumentation.targetContext.packageName
+// instrumentation.uiAutomation.executeShellCommand(
+// "pm clear $packageName"
+// ).close()
+// Thread.sleep(2000) // wait for clear to complete before relaunch
+// }
+
+
+ @Before
+ fun setup() {
+ if (!skipRelaunch) launchApp()
+ if (!skipOnboardingHandling) handleOnboardingIfPresent()
+ }
+
+ @After
+ fun stopRecording() {
+ // val video = ScreenRecorder.stop()
+ // allureVideo("Screen Recording - ${testName.methodName}", video)
+ }
+
+ private fun launchApp() {
+ val instrumentation = InstrumentationRegistry.getInstrumentation()
+ val context = instrumentation.targetContext
+
+ val device: UiDevice? = try {
+ UiDevice.getInstance(instrumentation)
+ } catch (e: IllegalStateException) {
+ android.util.Log.w("BaseTest", "UiAutomation already registered: ${e.message}")
+ null
+ }
+
+ device?.let { if (!it.isScreenOn) it.wakeUp() }
+
+ val intent = context.packageManager.getLaunchIntentForPackage("com.bitpay.wallet")
+ intent!!.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ context.startActivity(intent)
+
+ // CI emulators are typically slower than local hardware to load and
+ // render the JS bundle. Give RN's startup a generous window before any
+ // test attempts its first onView() lookup, rather than racing ahead.
+ device?.wait(Until.hasObject(By.pkg("com.bitpay.wallet").depth(0)), 20000)
+ Thread.sleep(2000)
+ }
+
+// private fun launchApp() {
+// val instrumentation = InstrumentationRegistry.getInstrumentation()
+// val context = instrumentation.targetContext
+//
+// val device: UiDevice? = try {
+// UiDevice.getInstance(instrumentation)
+// } catch (e: IllegalStateException) {
+// android.util.Log.w(
+// "BaseTest",
+// "UiAutomation already registered, skipping UiDevice setup: ${e.message}"
+// )
+// null
+// }
+//
+// device?.let {
+// if (!it.isScreenOn) {
+// it.wakeUp()
+// }
+// }
+//
+// val intent =
+// context.packageManager.getLaunchIntentForPackage("com.bitpay.wallet")
+//
+// intent!!.addFlags(
+// Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
+// )
+//
+// context.startActivity(intent)
+// }
+
+ /**
+ * If the onboarding "Continue without an account" screen is currently
+ * displayed, walk through the standard onboarding flow once so every
+ * test starts from a consistent post-onboarding state.
+ * If that screen isn't present (e.g. app is already past onboarding,
+ * or skipRelaunch left it on a different screen), this is a no-op.
+ */
+ private fun handleOnboardingIfPresent() {
+ if (!onboardingPage.isContinueWithoutAccountDisplayed()) {
+ return
+ }
+
+ onboardingPage.waitForPageToLoad()
+ onboardingPage.clickContinueWithoutAccount()
+ onboardingPage.clickSkip() // Skip turn on notifications
+
+ assertTrue(
+ "Protect Your Wallet was not displayed",
+ onboardingPage.verifyProtectYourWalletIsDisplayed()
+ )
+ onboardingPage.clickSkip()
+ }
+}
\ No newline at end of file
diff --git a/android/app/src/androidTest/java/com/bitpay/wallet/pages/AddYourCryptoOptionPage.kt b/android/app/src/androidTest/java/com/bitpay/wallet/pages/AddYourCryptoOptionPage.kt
new file mode 100644
index 0000000000..3c0e0d1f9f
--- /dev/null
+++ b/android/app/src/androidTest/java/com/bitpay/wallet/pages/AddYourCryptoOptionPage.kt
@@ -0,0 +1,27 @@
+package com.bitpay.wallet.pages
+
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.action.ViewActions.click
+import com.bitpay.wallet.utils.WaitUtils
+import com.bitpay.wallet.utils.WaitUtils.withTestId
+import androidx.test.espresso.matcher.ViewMatchers.withText
+import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
+import androidx.test.espresso.assertion.ViewAssertions.matches
+import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
+import org.hamcrest.Matchers.allOf
+
+class AddYourCryptoOptionPage {
+
+ // ---- Locators ----
+ private val importKeyButton = withTestId("creation-options-import-button")
+
+
+ // ---- Actions ----
+
+
+ fun clickImportKey() {
+ WaitUtils.waitForView(importKeyButton)
+ onView(importKeyButton).perform(click())
+ }
+
+}
\ No newline at end of file
diff --git a/android/app/src/androidTest/java/com/bitpay/wallet/pages/ConfirmPaymentPage.kt b/android/app/src/androidTest/java/com/bitpay/wallet/pages/ConfirmPaymentPage.kt
new file mode 100644
index 0000000000..236d07b380
--- /dev/null
+++ b/android/app/src/androidTest/java/com/bitpay/wallet/pages/ConfirmPaymentPage.kt
@@ -0,0 +1,43 @@
+package com.bitpay.wallet.pages
+
+import androidx.test.espresso.Espresso.onView
+import com.bitpay.wallet.utils.WaitUtils
+import com.bitpay.wallet.utils.WaitUtils.withTestId
+import androidx.test.espresso.action.ViewActions
+import androidx.test.espresso.action.ViewActions.click
+import androidx.test.espresso.action.ViewActions.pressImeActionButton
+import androidx.test.espresso.assertion.ViewAssertions.matches
+import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
+import androidx.test.espresso.matcher.ViewMatchers.withText
+
+class ConfirmPaymentPage {
+
+ // ---- Locators ----
+ private val confirmPaymentText = withText("Confirm Payment")
+
+ private val summaryText = withText("SUMMARY")
+
+
+ // ---- Actions ----
+
+ fun verifyConfirmPaymentTitleDisplayed(): Boolean {
+ return try {
+ WaitUtils.waitForView(confirmPaymentText)
+ onView(confirmPaymentText).check(matches(isDisplayed()))
+ true
+ } catch (e: Throwable) {
+ false
+ }
+ }
+
+ fun verifySummaryTextDisplayed(): Boolean {
+ return try {
+ WaitUtils.waitForView(summaryText)
+ onView(summaryText).check(matches(isDisplayed()))
+ true
+ } catch (e: Throwable) {
+ false
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/android/app/src/androidTest/java/com/bitpay/wallet/pages/HomePage.kt b/android/app/src/androidTest/java/com/bitpay/wallet/pages/HomePage.kt
new file mode 100644
index 0000000000..45058bd125
--- /dev/null
+++ b/android/app/src/androidTest/java/com/bitpay/wallet/pages/HomePage.kt
@@ -0,0 +1,74 @@
+package com.bitpay.wallet.pages
+
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.action.ViewActions.click
+import com.bitpay.wallet.utils.WaitUtils
+import com.bitpay.wallet.utils.WaitUtils.withTestId
+import androidx.test.espresso.matcher.ViewMatchers.withText
+import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
+import androidx.test.espresso.assertion.ViewAssertions.matches
+import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
+
+class HomePage {
+
+ // ---- Locators ----
+ private val portfolioBalanceText = withTestId("portfolio-balance-info-button")
+ private val myCryptoAddButton = withTestId("my-crypto-add-button")
+ private val buyButton = withTestId("buy-button")
+ private val sellButton = withTestId("sell-button")
+ private val swapButton = withTestId("swap-button")
+ private val receiveButton = withTestId("receive-button")
+ private val sendButton = withTestId("send-button")
+
+
+ // ---- Actions ----
+
+ fun waitForPageToLoad() {
+ WaitUtils.waitForView(
+ portfolioBalanceText,
+ timeoutMs = 120000
+ )
+ }
+
+ fun verifyPortfolioBalanceTextDisplayed(): Boolean {
+ return try {
+ WaitUtils.waitForView(portfolioBalanceText)
+ onView(portfolioBalanceText).check(matches(isDisplayed()))
+ true
+ } catch (e: Throwable) {
+ false
+ }
+ }
+
+ fun clickAddYourCrypto() {
+ WaitUtils.waitForView(myCryptoAddButton)
+ onView(myCryptoAddButton).perform(click())
+ }
+
+ fun clickBuy() {
+ WaitUtils.waitForView(buyButton)
+ onView(buyButton).perform(click())
+ }
+
+ fun clickSell() {
+ WaitUtils.waitForView(sellButton)
+ onView(sellButton).perform(click())
+ }
+
+ fun clickSwap() {
+ WaitUtils.waitForView(swapButton)
+ onView(swapButton).perform(click())
+ }
+
+ fun clickReceive() {
+ WaitUtils.waitForView(receiveButton)
+ onView(receiveButton).perform(click())
+ }
+
+ fun clickSend() {
+ // WaitUtils.waitForView(sendButton)
+ WaitUtils.waitForViewEnabled(sendButton)
+ onView(sendButton).perform(click())
+ }
+
+}
\ No newline at end of file
diff --git a/android/app/src/androidTest/java/com/bitpay/wallet/pages/ImportWalletPage.kt b/android/app/src/androidTest/java/com/bitpay/wallet/pages/ImportWalletPage.kt
new file mode 100644
index 0000000000..bfdf2ad0e7
--- /dev/null
+++ b/android/app/src/androidTest/java/com/bitpay/wallet/pages/ImportWalletPage.kt
@@ -0,0 +1,37 @@
+package com.bitpay.wallet.pages
+
+import androidx.test.espresso.Espresso.onView
+import com.bitpay.wallet.utils.WaitUtils
+import com.bitpay.wallet.utils.WaitUtils.withTestId
+import androidx.test.espresso.action.ViewActions
+import androidx.test.espresso.action.ViewActions.click
+import androidx.test.espresso.action.ViewActions.pressImeActionButton
+
+class ImportWalletPage {
+
+ // ---- Locators ----
+ private val recoveryPhraseInputField = withTestId("import-text-input")
+ private val importWalletButton = withTestId("import-wallet-button")
+
+
+ // ---- Actions ----
+
+ fun pressKeyboardTick() {
+ onView(recoveryPhraseInputField).perform(pressImeActionButton())
+ }
+
+ fun enterRecoveryPhrase(recoveryPhrase: String) {
+ WaitUtils.waitForView(recoveryPhraseInputField)
+ onView(recoveryPhraseInputField).perform(
+ ViewActions.click(),
+ ViewActions.typeText(recoveryPhrase),
+ ViewActions.closeSoftKeyboard()
+ )
+ }
+
+ fun clickImportWallet() {
+ WaitUtils.waitForView(importWalletButton)
+ onView(importWalletButton).perform(click())
+ }
+
+}
\ No newline at end of file
diff --git a/android/app/src/androidTest/java/com/bitpay/wallet/pages/KeyboardPage.kt b/android/app/src/androidTest/java/com/bitpay/wallet/pages/KeyboardPage.kt
new file mode 100644
index 0000000000..49c344dd11
--- /dev/null
+++ b/android/app/src/androidTest/java/com/bitpay/wallet/pages/KeyboardPage.kt
@@ -0,0 +1,114 @@
+package com.bitpay.wallet.pages
+
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.action.ViewActions
+import androidx.test.espresso.action.ViewActions.click
+import com.bitpay.wallet.utils.WaitUtils
+import com.bitpay.wallet.utils.WaitUtils.withTestId
+import org.hamcrest.Matchers.not
+import androidx.test.espresso.matcher.ViewMatchers.withText
+import androidx.test.espresso.matcher.ViewMatchers.hasSibling
+import androidx.test.espresso.matcher.ViewMatchers.withText
+import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
+import androidx.test.espresso.assertion.ViewAssertions.matches
+import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
+import org.hamcrest.Matchers.allOf
+
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.action.CoordinatesProvider
+import androidx.test.espresso.action.GeneralClickAction
+import androidx.test.espresso.action.Press
+import androidx.test.espresso.action.Tap
+import androidx.test.espresso.matcher.ViewMatchers.withText
+import android.view.View
+
+class KeyboardPage {
+
+ // ---- Locators ----
+
+ private val continueButton = withText("Continue")
+
+
+ // ---- Actions ----
+ fun enterAmount(amount: String = "0") {
+ for (char in amount) {
+ val key = allOf(
+ withText(char.toString()),
+ not(hasSibling(withText("BTC")))
+ )
+ WaitUtils.waitForView(key)
+ onView(key).perform(click())
+ }
+ }
+
+ fun clickContinue() {
+ WaitUtils.waitForViewEnabled(continueButton)
+ onView(continueButton).perform(click())
+ }
+
+
+ /**
+ * Backspace key does not expose a unique accessibility identifier.
+ * Calculate its position dynamically using the relative spacing
+ * between the keypad keys "6" and "9".
+ */
+ fun clickBackspace(count: Int = 1) {
+
+ val sixKey = withText("6")
+ val nineKey = withText("9")
+
+ WaitUtils.waitForView(sixKey)
+ WaitUtils.waitForView(nineKey)
+
+ var sixView: View? = null
+ var nineView: View? = null
+
+ onView(sixKey).perform(object : androidx.test.espresso.ViewAction {
+ override fun getConstraints() = org.hamcrest.Matchers.any(View::class.java)
+ override fun getDescription() = "capture six key view"
+ override fun perform(uiController: androidx.test.espresso.UiController, view: View) {
+ sixView = view
+ }
+ })
+
+ onView(nineKey).perform(object : androidx.test.espresso.ViewAction {
+ override fun getConstraints() = org.hamcrest.Matchers.any(View::class.java)
+ override fun getDescription() = "capture nine key view"
+ override fun perform(uiController: androidx.test.espresso.UiController, view: View) {
+ nineView = view
+ }
+ })
+
+ val sixLocation = IntArray(2)
+ val nineLocation = IntArray(2)
+ sixView!!.getLocationOnScreen(sixLocation)
+ nineView!!.getLocationOnScreen(nineLocation)
+
+ val sixMidY = sixLocation[1] + (sixView!!.height / 2)
+ val nineMidY = nineLocation[1] + (nineView!!.height / 2)
+ val nineMidX = nineLocation[0] + (nineView!!.width / 2)
+
+ val rowHeight = Math.abs(nineMidY - sixMidY)
+
+ val backspaceX = nineMidX.toFloat()
+ val backspaceY = (nineMidY + rowHeight).toFloat()
+
+ val backspaceCoordinatesProvider = CoordinatesProvider { _ ->
+ floatArrayOf(backspaceX, backspaceY)
+ }
+
+ repeat(count) {
+ onView(withText("9")).perform(
+ GeneralClickAction(
+ Tap.SINGLE,
+ backspaceCoordinatesProvider,
+ Press.FINGER,
+ android.view.InputDevice.SOURCE_UNKNOWN,
+ android.view.MotionEvent.BUTTON_PRIMARY
+ )
+ )
+ }
+ }
+
+
+}
\ No newline at end of file
diff --git a/android/app/src/androidTest/java/com/bitpay/wallet/pages/MyKeyPage.kt b/android/app/src/androidTest/java/com/bitpay/wallet/pages/MyKeyPage.kt
new file mode 100644
index 0000000000..f50418dfb8
--- /dev/null
+++ b/android/app/src/androidTest/java/com/bitpay/wallet/pages/MyKeyPage.kt
@@ -0,0 +1,50 @@
+package com.bitpay.wallet.pages
+
+import androidx.test.espresso.Espresso.onView
+import com.bitpay.wallet.utils.WaitUtils
+import com.bitpay.wallet.utils.WaitUtils.withTestId
+import androidx.test.espresso.action.ViewActions
+import androidx.test.espresso.action.ViewActions.click
+import androidx.test.espresso.action.ViewActions.pressImeActionButton
+import androidx.test.espresso.assertion.ViewAssertions.matches
+import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
+import androidx.test.espresso.matcher.ViewMatchers.withText
+
+class MyKeyPage {
+
+ // ---- Locators ----
+ private val myKeyText = withText("My Key")
+ private val bitcoinText = withText("Bitcoin")
+
+
+ // ---- Actions ----
+
+ fun waitForPageToLoad() {
+ WaitUtils.waitForView(
+ myKeyText,
+ timeoutMs = 900000
+ )
+ }
+
+ fun verifyMyKeyDisplayed(): Boolean {
+ return try {
+ WaitUtils.waitForView(myKeyText, 900000)
+ onView(myKeyText).check(matches(isDisplayed()))
+ true
+ } catch (e: Throwable) {
+ false
+ }
+ }
+
+ fun verifyBitcoinTextDisplayed(): Boolean {
+ return try {
+ WaitUtils.waitForView(bitcoinText)
+ onView(bitcoinText).check(matches(isDisplayed()))
+ true
+ } catch (e: Throwable) {
+ false
+ }
+ }
+
+
+}
\ No newline at end of file
diff --git a/android/app/src/androidTest/java/com/bitpay/wallet/pages/OnboardingPage.kt b/android/app/src/androidTest/java/com/bitpay/wallet/pages/OnboardingPage.kt
new file mode 100644
index 0000000000..b4b3cf1147
--- /dev/null
+++ b/android/app/src/androidTest/java/com/bitpay/wallet/pages/OnboardingPage.kt
@@ -0,0 +1,166 @@
+package com.bitpay.wallet.pages
+
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.action.ViewActions.click
+import com.bitpay.wallet.utils.WaitUtils
+import com.bitpay.wallet.utils.WaitUtils.withTestId
+import androidx.test.espresso.matcher.ViewMatchers.withText
+import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
+import androidx.test.espresso.assertion.ViewAssertions.matches
+import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
+import androidx.test.espresso.matcher.ViewMatchers.withResourceName
+import org.hamcrest.Matchers.allOf
+
+class OnboardingPage {
+
+ // ---- Locators ----
+ private val getStartedButton = withTestId("get-started-button")
+ private val continueWithoutAccountButton = withTestId("continue-without-an-account-button")
+ private val skipButton = withTestId("skip-button")
+ private val createKeyButton = withTestId("create-a-key-button")
+ private val alreadyHaveKeyButton = withTestId("i-already-have-a-key-button")
+ private val protectYourWalletText = withText("Protect your wallet")
+ private val backupKeyPromptText = withText("Would you like to backup your key?")
+ private val bottomSheetLaterButton = withTestId("bottom-notification-secondary-action-button")
+ private val bottomSheetBackupYourKeyButton =
+ withTestId("bottom-notification-primary-action-button")
+ private val backupRecoveryPhraseElement = withContentDescription("Backup your recovery phrase")
+
+ // private val iUnderstandCheckBox1 = withTestId("first-term-checkbox")
+
+ private val iUnderstandCheckBox1 = allOf(
+ withTestId("first-term-checkbox"),
+ withContentDescription("Checkbox")
+ )
+
+ private val iUnderstandCheckBox2 = allOf(
+ withTestId("second-term-checkbox"),
+ withContentDescription("Checkbox")
+ )
+
+ private val iUnderstandCheckBox3 = allOf(
+ withTestId("third-term-checkbox"),
+ withContentDescription("Checkbox")
+ )
+
+// private val iUnderstandCheckBox2 = withTestId("second-term-checkbox")
+// private val iUnderstandCheckBox3 = withTestId("third-term-checkbox")
+
+ // private val iUnderstandCheckBox2 = withTestId("second-term-checkbox")
+// private val iUnderstandCheckBox3 = withTestId("third-term-checkbox")
+ private val agreeAndContinueButton = withTestId("agree-and-continue-button")
+
+
+ // ---- Actions ----
+
+ fun waitForPageToLoad() {
+ WaitUtils.waitForView(
+ continueWithoutAccountButton,
+ timeoutMs = 600000
+ )
+ }
+
+ fun clickGetStarted() {
+ WaitUtils.waitForView(getStartedButton)
+ onView(getStartedButton).perform(click())
+ }
+
+ fun isContinueWithoutAccountDisplayed(): Boolean {
+ return try {
+ WaitUtils.waitForView(continueWithoutAccountButton, timeoutMs = 120000)
+ true
+ } catch (e: Throwable) {
+ android.util.Log.e("OnboardingPage", "isContinueWithoutAccountDisplayed failed", e)
+ false
+ }
+ }
+
+ fun clickContinueWithoutAccount() {
+ WaitUtils.waitForView(continueWithoutAccountButton)
+ onView(continueWithoutAccountButton).perform(click())
+ }
+
+ fun clickSkip() {
+ WaitUtils.waitForView(skipButton)
+ onView(skipButton).perform(click())
+ }
+
+ fun clickCreateKey() {
+ WaitUtils.waitForView(createKeyButton)
+ onView(createKeyButton).perform(click())
+ }
+
+ fun clickAlreadyHaveKey() {
+ WaitUtils.waitForView(alreadyHaveKeyButton)
+ onView(alreadyHaveKeyButton).perform(click())
+ }
+
+ fun verifyProtectYourWalletIsDisplayed(): Boolean {
+ return try {
+ WaitUtils.waitForView(protectYourWalletText)
+ onView(protectYourWalletText).check(matches(isDisplayed()))
+ true
+ } catch (e: Throwable) {
+ false
+ }
+ }
+
+ fun verifyBackupKeyPromptIsDisplayed(): Boolean {
+ return try {
+ WaitUtils.waitForView(backupKeyPromptText, timeoutMs = 600000)
+ onView(backupKeyPromptText).check(matches(isDisplayed()))
+ true
+ } catch (e: Throwable) {
+ false
+ }
+ }
+
+ fun clickBottomSheetBackUpYourKey() {
+ WaitUtils.waitForView(bottomSheetBackupYourKeyButton)
+ onView(bottomSheetBackupYourKeyButton).perform(click())
+ }
+
+ fun clickBottomSheetLater() {
+ WaitUtils.waitForView(bottomSheetLaterButton)
+ onView(bottomSheetLaterButton).perform(click())
+ }
+
+ fun clickBackupRecoveryPhrase() {
+ WaitUtils.waitForView(backupRecoveryPhraseElement)
+ onView(backupRecoveryPhraseElement).perform(click())
+ }
+
+ fun verifyIUnderstandCheckbox1Displayed(): Boolean {
+ return try {
+ WaitUtils.waitForView(iUnderstandCheckBox1, 900000)
+ onView(iUnderstandCheckBox1).check(matches(isDisplayed()))
+ true
+ } catch (e: Throwable) {
+ false
+ }
+ }
+
+ fun clickIUnderstandCheckbox1() {
+ WaitUtils.waitForView(iUnderstandCheckBox1)
+ onView(iUnderstandCheckBox1).perform(click())
+ Thread.sleep(300)
+ }
+
+ fun clickIUnderstandCheckbox2() {
+ WaitUtils.waitForView(iUnderstandCheckBox2)
+ onView(iUnderstandCheckBox2).perform(click())
+ Thread.sleep(300)
+ }
+
+ fun clickIUnderstandCheckbox3() {
+ WaitUtils.waitForView(iUnderstandCheckBox3)
+ onView(iUnderstandCheckBox3).perform(click())
+ Thread.sleep(300)
+ }
+
+ fun clickAgreeAndContinue() {
+ WaitUtils.waitForView(agreeAndContinueButton)
+ onView(agreeAndContinueButton).perform(click())
+ }
+
+}
\ No newline at end of file
diff --git a/android/app/src/androidTest/java/com/bitpay/wallet/pages/SelectCurrencyPage.kt b/android/app/src/androidTest/java/com/bitpay/wallet/pages/SelectCurrencyPage.kt
new file mode 100644
index 0000000000..e38fb9db8f
--- /dev/null
+++ b/android/app/src/androidTest/java/com/bitpay/wallet/pages/SelectCurrencyPage.kt
@@ -0,0 +1,186 @@
+package com.bitpay.wallet.pages
+
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.action.ViewActions
+import androidx.test.espresso.action.ViewActions.click
+import com.bitpay.wallet.utils.WaitUtils
+import com.bitpay.wallet.utils.WaitUtils.withTestId
+import androidx.test.espresso.matcher.ViewMatchers.withText
+import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
+import androidx.test.espresso.assertion.ViewAssertions.matches
+import androidx.test.espresso.matcher.ViewMatchers.withSubstring
+import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
+import com.bitpay.wallet.utils.WaitUtils.withIndex
+import org.hamcrest.Matchers.allOf
+
+class SelectCurrencyPage {
+
+ // ---- Locators ----
+ private val selectCurrencyText = withText("Select a Currency")
+ private val bitcoinText = withSubstring("Bitcoin")
+ private val ethereumText = withSubstring("Ethereum")
+ private val sendToText = withText("Send To")
+ private val sendToAddressInput = withTestId("send-to-address-input")
+ private val swapText = withText("Swap")
+ private val swapFrom = withTestId("swap-crypto-from-wallet-selector")
+ private val swapTo = withTestId("swap-crypto-to-wallet-selector")
+ private val selectKeyToDepositToText = withText("Select Key to Deposit to")
+ private val myKeyWallet = withIndex(withTestId("wallet-card-My Key"), 1)
+ private val evmAccount = withText("EVM Account")
+ private val enterSwapAmountButton =withTestId("swap-crypto-enter-amount-button")
+ private val minButton = withContentDescription("MIN")
+ private val swapChangellyTermsCheckbox =withTestId("swap-crypto-changelly-terms-checkbox")
+ private val slideToSwipeButton = withContentDescription("Slide to swap")
+
+ private val buyText = withText("Buy")
+ private val sellText = withText("Sell")
+ private val buyContinueButton = withTestId("button")
+ private val chooseCrypto = withText("Choose Crypto")
+
+
+
+
+
+
+
+
+
+ // ---- Actions ----
+ fun verifySelectCurrencyDisplayed(): Boolean {
+ return try {
+ WaitUtils.waitForView(selectCurrencyText)
+ onView(selectCurrencyText).check(matches(isDisplayed()))
+ true
+ } catch (e: Throwable) {
+ false
+ }
+ }
+
+ fun clickBitcoin() {
+ WaitUtils.waitForView(bitcoinText)
+ onView(bitcoinText).perform(click())
+ }
+
+ fun clickEthereum() {
+ WaitUtils.waitForView(ethereumText)
+ onView(ethereumText).perform(click())
+ }
+
+ fun verifySendToDisplayed(): Boolean {
+ return try {
+ WaitUtils.waitForView(sendToText)
+ onView(sendToText).check(matches(isDisplayed()))
+ true
+ } catch (e: Throwable) {
+ false
+ }
+ }
+
+ fun enterSendToAddress(sendToAddress: String) {
+ WaitUtils.waitForView(sendToAddressInput)
+ onView(sendToAddressInput).perform(
+ ViewActions.click(),
+ ViewActions.typeText(sendToAddress),
+ ViewActions.closeSoftKeyboard()
+ )
+ }
+
+ fun verifySwapTitleDisplayed(): Boolean {
+ return try {
+ WaitUtils.waitForView(swapText)
+ onView(swapText).check(matches(isDisplayed()))
+ true
+ } catch (e: Throwable) {
+ false
+ }
+ }
+
+ fun verifyBuyTitleDisplayed(): Boolean {
+ return try {
+ WaitUtils.waitForView(buyText)
+ onView(buyText).check(matches(isDisplayed()))
+ true
+ } catch (e: Throwable) {
+ false
+ }
+ }
+
+ fun verifySellTitleDisplayed(): Boolean {
+ return try {
+ WaitUtils.waitForView(sellText)
+ onView(sellText).check(matches(isDisplayed()))
+ true
+ } catch (e: Throwable) {
+ false
+ }
+ }
+
+ fun clickSwapFromOption() {
+ WaitUtils.waitForView(swapFrom)
+ onView(swapFrom).perform(click())
+ }
+
+ fun clickSwapToOption() {
+ WaitUtils.waitForView(swapTo)
+ onView(swapTo).perform(click())
+ }
+
+ fun verifySelectKeyToDepositToDisplayed(): Boolean {
+ return try {
+ WaitUtils.waitForView(selectKeyToDepositToText)
+ onView(selectKeyToDepositToText).check(matches(isDisplayed()))
+ true
+ } catch (e: Throwable) {
+ false
+ }
+ }
+
+ fun clickEnterSwapAmount() {
+ WaitUtils.waitForView(enterSwapAmountButton)
+ onView(enterSwapAmountButton).perform(click())
+ }
+
+ fun clickMinSwapAmount() {
+ WaitUtils.waitForView(minButton)
+ onView(minButton).perform(click())
+ }
+
+ fun clickSecondMyKeyWallet() {
+ WaitUtils.waitForView(myKeyWallet)
+ onView(myKeyWallet).perform(click())
+ }
+
+ fun clickEVMAccount() {
+ WaitUtils.waitForView(evmAccount)
+ onView(evmAccount).perform(click())
+ }
+
+ fun clickChangellyTermsCheckbox() {
+ WaitUtils.waitForView(swapChangellyTermsCheckbox)
+ onView(swapChangellyTermsCheckbox).perform(click())
+ }
+
+ fun verifySlideToSwapButtonEnabled(): Boolean {
+ return try {
+ WaitUtils.waitForViewEnabled(slideToSwipeButton)
+ true
+ } catch (e: Throwable) {
+ false
+ }
+ }
+
+ fun verifyBuyContinueButtonEnabled(): Boolean {
+ return try {
+ WaitUtils.waitForViewEnabled(buyContinueButton)
+ true
+ } catch (e: Throwable) {
+ false
+ }
+ }
+
+ fun clickChooseCrypto() {
+ WaitUtils.waitForView(chooseCrypto)
+ onView(chooseCrypto).perform(click())
+ }
+
+}
\ No newline at end of file
diff --git a/android/app/src/androidTest/java/com/bitpay/wallet/tests/Test591OnboardingCreateWallet.kt b/android/app/src/androidTest/java/com/bitpay/wallet/tests/Test591OnboardingCreateWallet.kt
new file mode 100644
index 0000000000..201ea1ab6a
--- /dev/null
+++ b/android/app/src/androidTest/java/com/bitpay/wallet/tests/Test591OnboardingCreateWallet.kt
@@ -0,0 +1,65 @@
+package com.bitpay.wallet.tests
+
+import com.bitpay.wallet.base.BaseTest
+import com.bitpay.wallet.pages.HomePage
+import com.bitpay.wallet.pages.OnboardingPage
+import com.bitpay.wallet.utils.allureStep
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.bitpay.wallet.utils.allureScreenshot
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+
+
+@RunWith(AndroidJUnit4::class)
+class Test591OnboardingCreateWallet : BaseTest() {
+ private val onboardingPage = OnboardingPage()
+ private val homePage = HomePage()
+
+ @Test
+ fun testOnboardingCreateWallet() {
+ allureStep("Click Create Key on onboarding screen") {
+ onboardingPage.clickCreateKey()
+ allureScreenshot("After Create Key clicked")
+ }
+
+ allureStep("Verify backup key prompt is displayed") {
+ assertTrue(
+ "Backup key prompt was not displayed",
+ onboardingPage.verifyBackupKeyPromptIsDisplayed()
+ )
+ allureScreenshot("Backup key prompt displayed")
+ }
+
+ allureStep("Skip backup prompt") {
+ onboardingPage.clickSkip() // Skip "Would you like to backup"
+ }
+
+ allureStep("Dismiss bottom sheet (Later)") {
+ onboardingPage.clickBottomSheetLater()
+ }
+
+ allureStep("Check all three 'I understand' checkboxes") {
+ onboardingPage.clickIUnderstandCheckbox1()
+ onboardingPage.clickIUnderstandCheckbox2()
+ onboardingPage.clickIUnderstandCheckbox3()
+ }
+
+ allureStep("Click Agree and Continue") {
+ onboardingPage.clickAgreeAndContinue()
+ }
+
+ allureStep("Wait for Home page to load") {
+ homePage.waitForPageToLoad()
+ }
+
+ allureStep("Verify portfolio balance is displayed on Home page") {
+ assertTrue(
+ "Home Page - Portfolio balance text not displayed",
+ homePage.verifyPortfolioBalanceTextDisplayed()
+ )
+ allureScreenshot("Final - Portfolio balance verified")
+ }
+
+ }
+}
diff --git a/android/app/src/androidTest/java/com/bitpay/wallet/tests/Test592ImportWalletRecoveryPhrase.kt b/android/app/src/androidTest/java/com/bitpay/wallet/tests/Test592ImportWalletRecoveryPhrase.kt
new file mode 100644
index 0000000000..7b724f8f2e
--- /dev/null
+++ b/android/app/src/androidTest/java/com/bitpay/wallet/tests/Test592ImportWalletRecoveryPhrase.kt
@@ -0,0 +1,102 @@
+package com.bitpay.wallet.tests
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.bitpay.wallet.base.BaseTest
+import com.bitpay.wallet.pages.AddYourCryptoOptionPage
+import com.bitpay.wallet.pages.HomePage
+import com.bitpay.wallet.pages.ImportWalletPage
+import com.bitpay.wallet.pages.MyKeyPage
+import com.bitpay.wallet.pages.OnboardingPage
+import com.bitpay.wallet.utils.allureScreenshot
+import com.bitpay.wallet.utils.allureStep
+import org.junit.Assert.assertTrue
+import org.junit.BeforeClass
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class Test592ImportWalletRecoveryPhrase : BaseTest() {
+
+ private val onboardingPage = OnboardingPage()
+ private val homePage = HomePage()
+ private val addYourCryptoOptionPage = AddYourCryptoOptionPage()
+ private val importWalletPage = ImportWalletPage()
+ private val myKeyPage = MyKeyPage()
+
+
+ @Test
+ fun testImportWalletRecoveryPhrase() {
+
+ allureStep("Click Create Key on onboarding screen") {
+ onboardingPage.clickCreateKey()
+ }
+
+ allureStep("Verify backup key prompt is displayed") {
+ assertTrue(
+ "Backup key prompt was not displayed",
+ onboardingPage.verifyBackupKeyPromptIsDisplayed()
+ )
+ allureScreenshot("Backup key prompt displayed")
+ }
+
+ allureStep("Skip backup prompt") {
+ onboardingPage.clickSkip() // Skip "Would you like to backup"
+ }
+
+ allureStep("Dismiss bottom sheet (Later)") {
+ onboardingPage.clickBottomSheetLater()
+ }
+
+ allureStep("Check all three 'I understand' checkboxes") {
+ onboardingPage.clickIUnderstandCheckbox1()
+ onboardingPage.clickIUnderstandCheckbox2()
+ onboardingPage.clickIUnderstandCheckbox3()
+ }
+
+ allureStep("Click Agree and Continue") {
+ onboardingPage.clickAgreeAndContinue()
+ }
+
+ allureStep("Wait for Home page to load") {
+ homePage.waitForPageToLoad()
+ allureScreenshot("Home page loaded")
+ }
+
+ allureStep("Verify portfolio balance is displayed on Home page") {
+ assertTrue(
+ "Home Page - Portfolio balance text not displayed",
+ homePage.verifyPortfolioBalanceTextDisplayed()
+ )
+ }
+
+ allureStep("Click Add Your Crypto on Home page") {
+ homePage.clickAddYourCrypto()
+ }
+
+ allureStep("Click Import Key option") {
+ addYourCryptoOptionPage.clickImportKey()
+ }
+
+ allureStep("Enter recovery phrase and import wallet") {
+ importWalletPage.enterRecoveryPhrase("hobby short divert lady spare quit act settle body town license alone")
+ allureScreenshot("Recovery phrase entered")
+ importWalletPage.clickImportWallet()
+ }
+
+ allureStep("Verify wallet import succeeded - My Key page displayed") {
+ assertTrue(
+ "Wallet Import Failed - My Key not displayed",
+ myKeyPage.verifyMyKeyDisplayed()
+ )
+ allureScreenshot("My Key page displayed after import")
+ }
+
+ allureStep("Verify Bitcoin is displayed in My Key page") {
+ assertTrue(
+ "Bitcoin not displayed in My Key page",
+ myKeyPage.verifyBitcoinTextDisplayed()
+ )
+ allureScreenshot("Final - Bitcoin shown in My Key page")
+ }
+ }
+}
\ No newline at end of file
diff --git a/android/app/src/androidTest/java/com/bitpay/wallet/tests/Test593SendBTC.kt b/android/app/src/androidTest/java/com/bitpay/wallet/tests/Test593SendBTC.kt
new file mode 100644
index 0000000000..103921a811
--- /dev/null
+++ b/android/app/src/androidTest/java/com/bitpay/wallet/tests/Test593SendBTC.kt
@@ -0,0 +1,122 @@
+package com.bitpay.wallet.tests
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.bitpay.wallet.base.BaseTest
+import com.bitpay.wallet.pages.AddYourCryptoOptionPage
+import com.bitpay.wallet.pages.ConfirmPaymentPage
+import com.bitpay.wallet.pages.HomePage
+import com.bitpay.wallet.pages.ImportWalletPage
+import com.bitpay.wallet.pages.KeyboardPage
+import com.bitpay.wallet.pages.MyKeyPage
+import com.bitpay.wallet.pages.OnboardingPage
+import com.bitpay.wallet.pages.SelectCurrencyPage
+import com.bitpay.wallet.utils.allureScreenshot
+import com.bitpay.wallet.utils.allureStep
+import org.junit.Assert.assertTrue
+import org.junit.BeforeClass
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class Test593SendBTC : BaseTest() {
+
+ private val onboardingPage = OnboardingPage()
+ private val homePage = HomePage()
+ private val addYourCryptoOptionPage = AddYourCryptoOptionPage()
+ private val importWalletPage = ImportWalletPage()
+ private val myKeyPage = MyKeyPage()
+ private val selectCurrencyPage = SelectCurrencyPage()
+ private val keyboardPage = KeyboardPage()
+ private val confirmPaymentPage = ConfirmPaymentPage()
+
+ @Test
+ fun testSendBTC() {
+ allureStep("Click 'Already have a key' on onboarding screen") {
+ onboardingPage.clickAlreadyHaveKey()
+ }
+
+ allureStep("Enter recovery phrase and import wallet") {
+ importWalletPage.enterRecoveryPhrase("hobby short divert lady spare quit act settle body town license alone")
+ allureScreenshot("Recovery phrase entered")
+ importWalletPage.clickImportWallet()
+ }
+
+ allureStep("Verify 'I understand' checkbox 1 is displayed") {
+ onboardingPage.verifyIUnderstandCheckbox1Displayed()
+ }
+
+ allureStep("Check all three 'I understand' checkboxes") {
+ onboardingPage.clickIUnderstandCheckbox1()
+ onboardingPage.clickIUnderstandCheckbox2()
+ onboardingPage.clickIUnderstandCheckbox3()
+ }
+
+ allureStep("Click Agree and Continue") {
+ onboardingPage.clickAgreeAndContinue()
+ }
+
+ allureStep("Wait for Home page to load") {
+ homePage.waitForPageToLoad()
+ allureScreenshot("Home page loaded")
+ }
+
+ allureStep("Verify portfolio balance is displayed on Home page") {
+ assertTrue(
+ "Home Page - Portfolio balance text not displayed",
+ homePage.verifyPortfolioBalanceTextDisplayed()
+ )
+ }
+
+ allureStep("Click Send on Home page") {
+ homePage.clickSend()
+ }
+
+ allureStep("Verify Select Currency page is displayed") {
+ assertTrue(
+ "Select Currency page not displayed",
+ selectCurrencyPage.verifySelectCurrencyDisplayed()
+ )
+ assertTrue(
+ "Select Currency page not displayed",
+ selectCurrencyPage.verifySelectCurrencyDisplayed()
+ )
+ allureScreenshot("Select Currency page displayed")
+ }
+
+ allureStep("Click Bitcoin currency option") {
+ selectCurrencyPage.clickBitcoin()
+ }
+
+ allureStep("Verify Send To page is displayed") {
+ assertTrue(
+ "Send To page not displayed",
+ selectCurrencyPage.verifySendToDisplayed()
+ )
+ }
+
+ allureStep("Enter Send To address") {
+ selectCurrencyPage.enterSendToAddress("bc1q0effzahtsn685tyjppgukpvfhv37hrlm4g67ws")
+ }
+
+ allureStep("Enter amount and click Continue") {
+ keyboardPage.enterAmount("0.00005")
+ keyboardPage.clickContinue()
+ }
+
+ allureStep("Verify Confirm Payment page is displayed") {
+ assertTrue(
+ "Confirm Payment page not displayed",
+ confirmPaymentPage.verifyConfirmPaymentTitleDisplayed(),
+ )
+ allureScreenshot("Confirm Payment page displayed")
+ }
+
+ allureStep("Verify Summary text is displayed on Confirm Payment page") {
+ assertTrue(
+ "Confirm Payment - Summary page not displayed",
+ confirmPaymentPage.verifySummaryTextDisplayed()
+ )
+ allureScreenshot("Final - Summary displayed before sending")
+ }
+ }
+}
\ No newline at end of file
diff --git a/android/app/src/androidTest/java/com/bitpay/wallet/tests/Test594SwapBTC.kt b/android/app/src/androidTest/java/com/bitpay/wallet/tests/Test594SwapBTC.kt
new file mode 100644
index 0000000000..421af00aa4
--- /dev/null
+++ b/android/app/src/androidTest/java/com/bitpay/wallet/tests/Test594SwapBTC.kt
@@ -0,0 +1,118 @@
+package com.bitpay.wallet.tests
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.bitpay.wallet.base.BaseTest
+import com.bitpay.wallet.pages.AddYourCryptoOptionPage
+import com.bitpay.wallet.pages.HomePage
+import com.bitpay.wallet.pages.ImportWalletPage
+import com.bitpay.wallet.pages.MyKeyPage
+import com.bitpay.wallet.pages.OnboardingPage
+import com.bitpay.wallet.pages.SelectCurrencyPage
+import com.bitpay.wallet.utils.allureScreenshot
+import com.bitpay.wallet.utils.allureStep
+import org.junit.Assert.assertTrue
+import org.junit.BeforeClass
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class Test594SwapBTC : BaseTest() {
+
+ private val onboardingPage = OnboardingPage()
+ private val homePage = HomePage()
+ private val addYourCryptoOptionPage = AddYourCryptoOptionPage()
+ private val importWalletPage = ImportWalletPage()
+ private val myKeyPage = MyKeyPage()
+ private val selectCurrencyPage = SelectCurrencyPage()
+
+ @Test
+ fun testBTCSwap() {
+ allureStep("Click 'Already have a key' on onboarding screen") {
+ onboardingPage.clickAlreadyHaveKey()
+ }
+
+ allureStep("Enter recovery phrase and import wallet") {
+ importWalletPage.enterRecoveryPhrase("hobby short divert lady spare quit act settle body town license alone")
+ allureScreenshot("Recovery phrase entered")
+ importWalletPage.clickImportWallet()
+ }
+
+ allureStep("Verify 'I understand' checkbox 1 is displayed") {
+ onboardingPage.verifyIUnderstandCheckbox1Displayed()
+ }
+
+ allureStep("Check all three 'I understand' checkboxes") {
+ onboardingPage.clickIUnderstandCheckbox1()
+ onboardingPage.clickIUnderstandCheckbox2()
+ onboardingPage.clickIUnderstandCheckbox3()
+ }
+
+ allureStep("Click Agree and Continue") {
+ onboardingPage.clickAgreeAndContinue()
+ }
+
+ allureStep("Wait for Home page to load") {
+ homePage.waitForPageToLoad()
+ allureScreenshot("Home page loaded")
+ }
+
+ allureStep("Verify portfolio balance is displayed on Home page") {
+ assertTrue(
+ "Home Page - Portfolio balance text not displayed",
+ homePage.verifyPortfolioBalanceTextDisplayed()
+ )
+ }
+
+ allureStep("Click Swap on Home page") {
+ homePage.clickSwap()
+ }
+
+ allureStep("Verify Swap page is displayed") {
+ assertTrue(
+ "Swap page not displayed",
+ selectCurrencyPage.verifySwapTitleDisplayed()
+ )
+ allureScreenshot("Swap page displayed")
+ }
+
+ allureStep("Select Bitcoin as Swap From currency") {
+ selectCurrencyPage.clickSwapFromOption()
+ selectCurrencyPage.clickBitcoin()
+ }
+
+ allureStep("Select Ethereum as Swap To currency") {
+ selectCurrencyPage.clickSwapToOption()
+ selectCurrencyPage.clickEthereum()
+ }
+
+ allureStep("Verify Select Key To Deposit To option is displayed") {
+ assertTrue(
+ "Select Key To Deposit To option not displayed",
+ selectCurrencyPage.verifySelectKeyToDepositToDisplayed()
+ )
+ }
+
+ allureStep("Select second My Key wallet and EVM account") {
+ selectCurrencyPage.clickSecondMyKeyWallet()
+ selectCurrencyPage.clickEVMAccount()
+ }
+
+ allureStep("Enter minimum swap amount") {
+ selectCurrencyPage.clickEnterSwapAmount()
+ selectCurrencyPage.clickMinSwapAmount()
+ allureScreenshot("Minimum swap amount entered")
+ }
+
+ allureStep("Accept Changelly terms checkbox") {
+ selectCurrencyPage.clickChangellyTermsCheckbox()
+ }
+
+ allureStep("Verify Slide to Swap button is enabled") {
+ assertTrue(
+ "Slide to Swap button not enabled",
+ selectCurrencyPage.verifySlideToSwapButtonEnabled()
+ )
+ allureScreenshot("Final - Slide to Swap button enabled")
+ }
+ }
+}
\ No newline at end of file
diff --git a/android/app/src/androidTest/java/com/bitpay/wallet/tests/Test595SellBTC.kt b/android/app/src/androidTest/java/com/bitpay/wallet/tests/Test595SellBTC.kt
new file mode 100644
index 0000000000..74a9deb690
--- /dev/null
+++ b/android/app/src/androidTest/java/com/bitpay/wallet/tests/Test595SellBTC.kt
@@ -0,0 +1,98 @@
+package com.bitpay.wallet.tests
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.bitpay.wallet.base.BaseTest
+import com.bitpay.wallet.pages.AddYourCryptoOptionPage
+import com.bitpay.wallet.pages.HomePage
+import com.bitpay.wallet.pages.ImportWalletPage
+import com.bitpay.wallet.pages.KeyboardPage
+import com.bitpay.wallet.pages.MyKeyPage
+import com.bitpay.wallet.pages.OnboardingPage
+import com.bitpay.wallet.pages.SelectCurrencyPage
+import com.bitpay.wallet.utils.allureScreenshot
+import com.bitpay.wallet.utils.allureStep
+import org.junit.Assert.assertTrue
+import org.junit.BeforeClass
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class Test595SellBTC : BaseTest() {
+
+ private val onboardingPage = OnboardingPage()
+ private val homePage = HomePage()
+ private val addYourCryptoOptionPage = AddYourCryptoOptionPage()
+ private val importWalletPage = ImportWalletPage()
+ private val myKeyPage = MyKeyPage()
+ private val selectCurrencyPage = SelectCurrencyPage()
+ private val keyboardPage = KeyboardPage()
+
+ @Test
+ fun testBTCSell() {
+ allureStep("Click 'Already have a key' on onboarding screen") {
+ onboardingPage.clickAlreadyHaveKey()
+ }
+
+ allureStep("Enter recovery phrase and import wallet") {
+ importWalletPage.enterRecoveryPhrase("hobby short divert lady spare quit act settle body town license alone")
+ allureScreenshot("Recovery phrase entered")
+ importWalletPage.clickImportWallet()
+ }
+
+ allureStep("Verify 'I understand' checkbox 1 is displayed") {
+ onboardingPage.verifyIUnderstandCheckbox1Displayed()
+ }
+
+ allureStep("Check all three 'I understand' checkboxes") {
+ onboardingPage.clickIUnderstandCheckbox1()
+ onboardingPage.clickIUnderstandCheckbox2()
+ onboardingPage.clickIUnderstandCheckbox3()
+ }
+
+ allureStep("Click Agree and Continue") {
+ onboardingPage.clickAgreeAndContinue()
+ }
+
+ allureStep("Wait for Home page to load") {
+ homePage.waitForPageToLoad()
+ allureScreenshot("Home page loaded")
+ }
+
+ allureStep("Verify portfolio balance is displayed on Home page") {
+ assertTrue(
+ "Home Page - Portfolio balance text not displayed",
+ homePage.verifyPortfolioBalanceTextDisplayed()
+ )
+ }
+
+ allureStep("Click Sell on Home page") {
+ homePage.clickSell()
+ }
+
+ allureStep("Verify Sell page is displayed") {
+ assertTrue(
+ "Sell page not displayed",
+ selectCurrencyPage.verifySellTitleDisplayed()
+ )
+ allureScreenshot("Sell page displayed")
+ }
+
+ allureStep("Enter sell amount") {
+ keyboardPage.enterAmount("0.0007")
+ }
+
+ allureStep("Choose Bitcoin as the crypto to sell") {
+ selectCurrencyPage.clickChooseCrypto()
+ selectCurrencyPage.clickBitcoin()
+ allureScreenshot("Bitcoin selected for sell")
+ }
+
+ allureStep("Verify Continue button is enabled") {
+ assertTrue(
+ "Buy - Continue button not enabled",
+ selectCurrencyPage.verifyBuyContinueButtonEnabled()
+ )
+ allureScreenshot("Final - Continue button enabled")
+ }
+ }
+}
\ No newline at end of file
diff --git a/android/app/src/androidTest/java/com/bitpay/wallet/tests/Test596BuyBTC.kt b/android/app/src/androidTest/java/com/bitpay/wallet/tests/Test596BuyBTC.kt
new file mode 100644
index 0000000000..1f34bffbfb
--- /dev/null
+++ b/android/app/src/androidTest/java/com/bitpay/wallet/tests/Test596BuyBTC.kt
@@ -0,0 +1,110 @@
+package com.bitpay.wallet.tests
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.bitpay.wallet.base.BaseTest
+import com.bitpay.wallet.pages.AddYourCryptoOptionPage
+import com.bitpay.wallet.pages.HomePage
+import com.bitpay.wallet.pages.ImportWalletPage
+import com.bitpay.wallet.pages.KeyboardPage
+import com.bitpay.wallet.pages.MyKeyPage
+import com.bitpay.wallet.pages.OnboardingPage
+import com.bitpay.wallet.pages.SelectCurrencyPage
+import com.bitpay.wallet.utils.allureScreenshot
+import com.bitpay.wallet.utils.allureStep
+import org.junit.Assert.assertTrue
+import org.junit.BeforeClass
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class Test596BuyBTC : BaseTest() {
+
+ private val onboardingPage = OnboardingPage()
+ private val homePage = HomePage()
+ private val addYourCryptoOptionPage = AddYourCryptoOptionPage()
+ private val importWalletPage = ImportWalletPage()
+ private val myKeyPage = MyKeyPage()
+ private val selectCurrencyPage = SelectCurrencyPage()
+ private val keyboardPage = KeyboardPage()
+
+ @Test
+ fun testBTCBuy() {
+ allureStep("Click 'Already have a key' on onboarding screen") {
+ onboardingPage.clickAlreadyHaveKey()
+ }
+
+ allureStep("Enter recovery phrase and import wallet") {
+ importWalletPage.enterRecoveryPhrase("hobby short divert lady spare quit act settle body town license alone")
+ allureScreenshot("Recovery phrase entered")
+ importWalletPage.clickImportWallet()
+ }
+
+ allureStep("Verify 'I understand' checkbox 1 is displayed") {
+ onboardingPage.verifyIUnderstandCheckbox1Displayed()
+ }
+
+ allureStep("Check all three 'I understand' checkboxes") {
+ onboardingPage.clickIUnderstandCheckbox1()
+ onboardingPage.clickIUnderstandCheckbox2()
+ onboardingPage.clickIUnderstandCheckbox3()
+ }
+
+ allureStep("Click Agree and Continue") {
+ onboardingPage.clickAgreeAndContinue()
+ }
+
+ allureStep("Wait for Home page to load") {
+ homePage.waitForPageToLoad()
+ allureScreenshot("Home page loaded")
+ }
+
+ allureStep("Verify portfolio balance is displayed on Home page") {
+ assertTrue(
+ "Home Page - Portfolio balance text not displayed",
+ homePage.verifyPortfolioBalanceTextDisplayed()
+ )
+ }
+
+ allureStep("Click Buy on Home page") {
+ homePage.clickBuy()
+ }
+
+ allureStep("Verify Buy page is displayed") {
+ assertTrue(
+ "Buy page not displayed",
+ selectCurrencyPage.verifyBuyTitleDisplayed()
+ )
+ allureScreenshot("Buy page displayed")
+ }
+
+ allureStep("Clear default amount and enter buy amount") {
+ keyboardPage.clickBackspace(3)
+ keyboardPage.enterAmount("30")
+ }
+
+ allureStep("Select Bitcoin as the crypto to buy") {
+ selectCurrencyPage.clickBitcoin()
+ selectCurrencyPage.clickBitcoin() // Select Crypto page
+ allureScreenshot("Bitcoin selected for buy")
+ }
+
+ allureStep("Verify Select Key To Deposit To option is displayed") {
+ assertTrue(
+ "Select Key To Deposit To option not displayed",
+ selectCurrencyPage.verifySelectKeyToDepositToDisplayed()
+ )
+ }
+
+ allureStep("Select second My Key wallet") {
+ selectCurrencyPage.clickSecondMyKeyWallet()
+ }
+
+ allureStep("Verify Continue button is enabled") {
+ assertTrue(
+ "Buy - Continue button not enabled",
+ selectCurrencyPage.verifyBuyContinueButtonEnabled()
+ )
+ allureScreenshot("Final - Continue button enabled")
+ }
+ }
+}
\ No newline at end of file
diff --git a/android/app/src/androidTest/java/com/bitpay/wallet/utils/AllureUtils.kt b/android/app/src/androidTest/java/com/bitpay/wallet/utils/AllureUtils.kt
new file mode 100644
index 0000000000..a02dadb971
--- /dev/null
+++ b/android/app/src/androidTest/java/com/bitpay/wallet/utils/AllureUtils.kt
@@ -0,0 +1,27 @@
+package com.bitpay.wallet.utils
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import io.qameta.allure.kotlin.Allure
+import java.io.File
+
+fun allureScreenshot(name: String = "Screenshot") {
+ val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
+ val context = InstrumentationRegistry.getInstrumentation().targetContext
+ val file = File(context.cacheDir, "screenshot_${System.currentTimeMillis()}.png")
+
+ val saved = device.takeScreenshot(file)
+ if (saved && file.exists()) {
+ Allure.attachment(name, file.inputStream())
+ }
+}
+
+inline fun allureStep(name: String, crossinline block: () -> Unit) {
+ Allure.step(name) { block() }
+}
+
+fun allureVideo(name: String, file: File?) {
+ if (file == null || !file.exists()) return
+ Allure.attachment(name, file.inputStream(), "video/mp4")
+ file.delete()
+}
\ No newline at end of file
diff --git a/android/app/src/androidTest/java/com/bitpay/wallet/utils/WaitUtils.kt b/android/app/src/androidTest/java/com/bitpay/wallet/utils/WaitUtils.kt
new file mode 100644
index 0000000000..7b09aeec89
--- /dev/null
+++ b/android/app/src/androidTest/java/com/bitpay/wallet/utils/WaitUtils.kt
@@ -0,0 +1,96 @@
+package com.bitpay.wallet.utils
+
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.assertion.ViewAssertions.matches
+import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
+import androidx.test.espresso.matcher.ViewMatchers.isEnabled
+import org.hamcrest.Matcher
+import android.view.View
+import androidx.test.espresso.matcher.BoundedMatcher
+import org.hamcrest.Description
+import org.hamcrest.TypeSafeMatcher
+
+
+object WaitUtils {
+
+ fun waitForView(
+ matcher: Matcher,
+ timeoutMs: Long = 30000,
+ intervalMs: Long = 500
+ ) {
+ val endTime = System.currentTimeMillis() + timeoutMs
+ var lastError: Throwable? = null
+
+ while (System.currentTimeMillis() < endTime) {
+ try {
+ onView(matcher).check(matches(isDisplayed()))
+ return // found it, exit
+ } catch (e: Throwable) {
+ // catch EVERYTHING - NoMatchingViewException, AssertionError,
+ // RootViewWithoutFocusException, anything else Espresso throws
+ lastError = e
+ Thread.sleep(intervalMs)
+ }
+ }
+
+ // timed out - throw the last real error so the failure message is useful
+ throw lastError ?: RuntimeException("View not found within ${timeoutMs}ms: $matcher")
+ }
+
+ fun waitForViewEnabled(
+ matcher: Matcher,
+ timeoutMs: Long = 120000,
+ intervalMs: Long = 500
+ ) {
+ val endTime = System.currentTimeMillis() + timeoutMs
+ var lastError: Throwable? = null
+
+ while (System.currentTimeMillis() < endTime) {
+ try {
+ onView(matcher).check(matches(isDisplayed())).check(matches(isEnabled()))
+ return
+ } catch (e: Throwable) {
+ lastError = e
+ Thread.sleep(intervalMs)
+ }
+ }
+
+ throw lastError ?: RuntimeException("View not enabled within ${timeoutMs}ms: $matcher")
+ }
+
+ /**
+ * React Native testIDs are surfaced to native Espresso as View.getTag(),
+ * NOT as an Android resource id. withResourceName()/withId() will never
+ * match them - this matcher checks the tag directly instead.
+ */
+ fun withTestId(testId: String): BoundedMatcher {
+ return object : BoundedMatcher(View::class.java) {
+ override fun describeTo(description: Description) {
+ description.appendText("with RN testID: $testId")
+ }
+
+ override fun matchesSafely(view: View): Boolean {
+ return testId == view.tag
+ }
+ }
+ }
+
+ fun withIndex(matcher: Matcher, index: Int): Matcher {
+ return object : TypeSafeMatcher() {
+ var currentIndex = 0
+
+ override fun describeTo(description: Description) {
+ description.appendText("with index: $index ")
+ matcher.describeTo(description)
+ }
+
+ override fun matchesSafely(item: T): Boolean {
+ val matched = matcher.matches(item)
+ if (matched && currentIndex++ == index) {
+ return true
+ }
+ return false
+ }
+ }
+ }
+}
diff --git a/android/app/src/androidTest/resources/allure.properties b/android/app/src/androidTest/resources/allure.properties
new file mode 100644
index 0000000000..890384fac0
--- /dev/null
+++ b/android/app/src/androidTest/resources/allure.properties
@@ -0,0 +1 @@
+allure.results.useTestStorage=true
\ No newline at end of file
diff --git a/android/app/src/main/java/com/bitpay/wallet/MainActivity.kt b/android/app/src/main/java/com/bitpay/wallet/MainActivity.kt
index ca753330d0..0ff11a675b 100644
--- a/android/app/src/main/java/com/bitpay/wallet/MainActivity.kt
+++ b/android/app/src/main/java/com/bitpay/wallet/MainActivity.kt
@@ -48,10 +48,15 @@ class MainActivity : ReactActivity() {
(application as MainApplication).addActivityToStack(this.javaClass)
window.apply {
- setFlags(
- WindowManager.LayoutParams.FLAG_SECURE,
- WindowManager.LayoutParams.FLAG_SECURE
- )
+ if (!BuildConfig.DEBUG) {
+ // Disable screenshots/screen recording only in production.
+ // Allow automation tools (Espresso/Appium/Layout Inspector)
+ // to capture screenshots in debug builds.
+ setFlags(
+ WindowManager.LayoutParams.FLAG_SECURE,
+ WindowManager.LayoutParams.FLAG_SECURE
+ )
+ }
if (Build.VERSION.SDK_INT in 19..20) {
setWindowFlag(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, true)
diff --git a/android/app/src/main/res/xml/network_security_config.xml b/android/app/src/main/res/xml/network_security_config.xml
index 8420bcbfa9..776e6d0846 100644
--- a/android/app/src/main/res/xml/network_security_config.xml
+++ b/android/app/src/main/res/xml/network_security_config.xml
@@ -1,7 +1,7 @@
-
-
+
+
localhost
@@ -11,4 +11,4 @@
-
\ No newline at end of file
+
diff --git a/android/build.gradle b/android/build.gradle
index f5086ca47e..c6b79f7e01 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -52,3 +52,46 @@ allprojects {
maven { url "https://dl.appsflyer.com" }
}
}
+
+gradle.afterProject { proj ->
+ if (proj != rootProject && proj.hasProperty('android')) {
+ proj.android {
+ if (defaultConfig.minSdkVersion == null || defaultConfig.minSdkVersion.apiLevel < 24) {
+ defaultConfig {
+ minSdkVersion 24
+ }
+ }
+
+ if (compileSdkVersion == null || (compileSdkVersion as String).replace("android-", "").toInteger() < 34) {
+ compileSdkVersion 36
+ }
+
+ packagingOptions {
+ // Native shared libraries that multiple RN modules bundle a
+ // copy of, which also exist inside react-android itself.
+ // Confirmed needed by: react-native-gesture-handler
+ // (libfbjni.so), react-native-webassembly (libc++_shared.so).
+ pickFirst 'lib/*/libfbjni.so'
+ pickFirst 'lib/*/libc++_shared.so'
+ pickFirst 'lib/*/libjsi.so'
+ pickFirst 'lib/*/libreactnative.so'
+ pickFirst 'lib/*/libhermes.so'
+
+ resources {
+ pickFirsts += 'META-INF/**/coroutines.pro'
+ excludes += [
+ 'META-INF/MANIFEST.MF',
+ 'META-INF/LICENSE',
+ 'META-INF/LICENSE.txt',
+ 'META-INF/LICENSE.md',
+ 'META-INF/LICENSE-notice.md',
+ 'META-INF/NOTICE',
+ 'META-INF/NOTICE.txt',
+ 'META-INF/DEPENDENCIES',
+ 'META-INF/*.kotlin_module'
+ ]
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/index.js b/index.js
index 5e576ec3ea..939c866b9b 100644
--- a/index.js
+++ b/index.js
@@ -7,7 +7,7 @@ import '@walletconnect/react-native-compat';
import {AppRegistry, Alert, StatusBar, Appearance, LogBox} from 'react-native';
import {IS_MAESTRO} from '@env';
-if (IS_MAESTRO === 'true') {
+if (IS_MAESTRO === 'true' || __DEV__) {
LogBox.ignoreAllLogs();
}
import Root from './src/Root';
diff --git a/ios/BitPayApp.xcodeproj/project.pbxproj b/ios/BitPayApp.xcodeproj/project.pbxproj
index 8665f7982f..f2402f174d 100644
--- a/ios/BitPayApp.xcodeproj/project.pbxproj
+++ b/ios/BitPayApp.xcodeproj/project.pbxproj
@@ -14,7 +14,7 @@
256B92C82AC0ED0D00C1DF97 /* bitpay-recovery-phrase-template.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 256B92C72AC0ED0D00C1DF97 /* bitpay-recovery-phrase-template.pdf */; };
4054F0E729AEFAF0009BBC9C /* AllowedUrlPrefixProtocol.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4054F0E529AEFAEF009BBC9C /* AllowedUrlPrefixProtocol.mm */; };
523EFC4FD62CB0CD2FBD02FC /* libPods-BitPayApp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B1AA7DD4226DBC3478258808 /* libPods-BitPayApp.a */; };
- 52C9714B9004BCE190EA469D /* BundleHash.swift in Sources */ = {isa = PBXBuildFile; fileRef = E30DDDCF29E5E048D75CCF1A /* BundleHash.swift */; };
+ 52C9714B9004BCE190EA469D /* BitPayApp/BundleHash.swift in Sources */ = {isa = PBXBuildFile; fileRef = E30DDDCF29E5E048D75CCF1A /* BitPayApp/BundleHash.swift */; };
5B31AAFE2786387B002765E1 /* RCTDosh.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B31AAFD2786387B002765E1 /* RCTDosh.mm */; };
5B31AB5F2786515A002765E1 /* Dosh.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B31AB5E2786515A002765E1 /* Dosh.swift */; };
5B5631782838854000C395A8 /* PassKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5B5631772838854000C395A8 /* PassKit.framework */; };
@@ -24,6 +24,30 @@
6D7C95E72E9007E200F0D481 /* RCTPushPermissionManager.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6D7C95E62E9007E200F0D481 /* RCTPushPermissionManager.mm */; };
71BABDA4524A43E98A190C87 /* Archivo-Light.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 6D649751C2644FDCA18A5547 /* Archivo-Light.ttf */; };
761780ED2CA45674006654EE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 761780EC2CA45674006654EE /* AppDelegate.swift */; };
+ CD1957ED2FB37AD40072A9AE /* Test595_BasicSellBTC_.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD1957E92FB37AD40072A9AE /* Test595_BasicSellBTC_.swift */; };
+ CD1957EE2FB37AD40072A9AE /* Test592_ImportWalletRecoveryPhrase.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD1957E62FB37AD40072A9AE /* Test592_ImportWalletRecoveryPhrase.swift */; };
+ CD1957EF2FB37AD40072A9AE /* MykeyPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD1957DB2FB37AD40072A9AE /* MykeyPage.swift */; };
+ CD1957F02FB37AD40072A9AE /* BuyPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD1957D62FB37AD40072A9AE /* BuyPage.swift */; };
+ CD1957F12FB37AD40072A9AE /* SellPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD1957E12FB37AD40072A9AE /* SellPage.swift */; };
+ CD1957F22FB37AD40072A9AE /* Test591_OnboardingCreateWallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD1957E52FB37AD40072A9AE /* Test591_OnboardingCreateWallet.swift */; };
+ CD1957F32FB37AD40072A9AE /* Test596_BasicBuyBTC_.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD1957EA2FB37AD40072A9AE /* Test596_BasicBuyBTC_.swift */; };
+ CD1957F42FB37AD40072A9AE /* NewRecoveryPhrasePage.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD1957DC2FB37AD40072A9AE /* NewRecoveryPhrasePage.swift */; };
+ CD1957F52FB37AD40072A9AE /* VerifyYourPhrasePage.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD1957E32FB37AD40072A9AE /* VerifyYourPhrasePage.swift */; };
+ CD1957F62FB37AD40072A9AE /* ConfirmPaymentPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD1957D72FB37AD40072A9AE /* ConfirmPaymentPage.swift */; };
+ CD1957F72FB37AD40072A9AE /* AllureXCTestSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD1957D32FB37AD40072A9AE /* AllureXCTestSupport.swift */; };
+ CD1957F82FB37AD40072A9AE /* EnterAmountPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD1957D82FB37AD40072A9AE /* EnterAmountPage.swift */; };
+ CD1957F92FB37AD40072A9AE /* Test594_BasicSwapBTC.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD1957E82FB37AD40072A9AE /* Test594_BasicSwapBTC.swift */; };
+ CD1957FA2FB37AD40072A9AE /* AddCryptoOptionPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD1957D52FB37AD40072A9AE /* AddCryptoOptionPage.swift */; };
+ CD1957FB2FB37AD40072A9AE /* OnboardingPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD1957DD2FB37AD40072A9AE /* OnboardingPage.swift */; };
+ CD1957FC2FB37AD40072A9AE /* ImportRecoveryPhrasePage.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD1957DA2FB37AD40072A9AE /* ImportRecoveryPhrasePage.swift */; };
+ CD1957FD2FB37AD40072A9AE /* PortfolioBalancePage.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD1957DE2FB37AD40072A9AE /* PortfolioBalancePage.swift */; };
+ CD1957FE2FB37AD40072A9AE /* SwapPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD1957E22FB37AD40072A9AE /* SwapPage.swift */; };
+ CD1957FF2FB37AD40072A9AE /* Test593_BTCConfirmPaymentScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD1957E72FB37AD40072A9AE /* Test593_BTCConfirmPaymentScreen.swift */; };
+ CD1958002FB37AD40072A9AE /* SelectKeyToDepositPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD1957E02FB37AD40072A9AE /* SelectKeyToDepositPage.swift */; };
+ CD1958012FB37AD40072A9AE /* SelectCurrencyPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD1957DF2FB37AD40072A9AE /* SelectCurrencyPage.swift */; };
+ CD1958022FB37AD40072A9AE /* HomePage.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD1957D92FB37AD40072A9AE /* HomePage.swift */; };
+ CD1958032FB37AD40072A9AE /* TestPlan.xctestplan in Resources */ = {isa = PBXBuildFile; fileRef = CD1957EB2FB37AD40072A9AE /* TestPlan.xctestplan */; };
+ CD9683A82FB70CCC00192CFF /* AppFlows.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD9683A72FB70CC700192CFF /* AppFlows.swift */; };
D34892F44CAB44409EC335A9 /* Archivo-Black.ttf in Resources */ = {isa = PBXBuildFile; fileRef = F4435EB64D2448EC885B3036 /* Archivo-Black.ttf */; };
D9739196BA804EA29143DA96 /* Archivo-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 715670CB346B45A7A9EC2044 /* Archivo-Bold.ttf */; };
E6807D9059A643129784A5C3 /* Archivo-Thin.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 4DCF32F2D2364E70AD2F1E68 /* Archivo-Thin.ttf */; };
@@ -32,6 +56,16 @@
F64C0BF216CE4402A51A5ACC /* Archivo-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = BCDAB70057C84109A9201936 /* Archivo-Regular.ttf */; };
/* End PBXBuildFile section */
+/* Begin PBXContainerItemProxy section */
+ CDF0850D2FACD8A500CA4C1B /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 13B07F861A680F5B00A75B9A;
+ remoteInfo = BitPayApp;
+ };
+/* End PBXContainerItemProxy section */
+
/* Begin PBXFileReference section */
13B07F961A680F5B00A75B9A /* BitPayApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BitPayApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = BitPayApp/Images.xcassets; sourceTree = ""; };
@@ -52,7 +86,7 @@
5BCF1C342740B8B2006D872E /* BootSplash.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = BootSplash.storyboard; path = BitPayApp/BootSplash.storyboard; sourceTree = ""; };
5BEA4BEE2838400700AB2121 /* RCTPaymentPass.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTPaymentPass.h; sourceTree = ""; };
5BEA4BEF2838402700AB2121 /* RCTPaymentPass.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RCTPaymentPass.mm; sourceTree = ""; };
- 5D5D40A7E7B5265D652187F9 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = BitPayApp/PrivacyInfo.xcprivacy; sourceTree = ""; };
+ 5D5D40A7E7B5265D652187F9 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = BitPayApp/PrivacyInfo.xcprivacy; sourceTree = ""; };
6D615C4729725FD70063DB8E /* RCTTimer.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTimer.mm; sourceTree = ""; };
6D615C4829725FD70063DB8E /* RCTTimer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTimer.h; sourceTree = ""; };
6D649751C2644FDCA18A5547 /* Archivo-Light.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Archivo-Light.ttf"; path = "../assets/fonts/Archivo-Light.ttf"; sourceTree = ""; };
@@ -61,7 +95,32 @@
761780EC2CA45674006654EE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = BitPayApp/AppDelegate.swift; sourceTree = ""; };
B1AA7DD4226DBC3478258808 /* libPods-BitPayApp.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-BitPayApp.a"; sourceTree = BUILT_PRODUCTS_DIR; };
BCDAB70057C84109A9201936 /* Archivo-Regular.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Archivo-Regular.ttf"; path = "../assets/fonts/Archivo-Regular.ttf"; sourceTree = ""; };
- E30DDDCF29E5E048D75CCF1A /* BundleHash.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BitPayApp/BundleHash.swift; sourceTree = ""; };
+ CD1957D32FB37AD40072A9AE /* AllureXCTestSupport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllureXCTestSupport.swift; sourceTree = ""; };
+ CD1957D52FB37AD40072A9AE /* AddCryptoOptionPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddCryptoOptionPage.swift; sourceTree = ""; };
+ CD1957D62FB37AD40072A9AE /* BuyPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuyPage.swift; sourceTree = ""; };
+ CD1957D72FB37AD40072A9AE /* ConfirmPaymentPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmPaymentPage.swift; sourceTree = ""; };
+ CD1957D82FB37AD40072A9AE /* EnterAmountPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnterAmountPage.swift; sourceTree = ""; };
+ CD1957D92FB37AD40072A9AE /* HomePage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomePage.swift; sourceTree = ""; };
+ CD1957DA2FB37AD40072A9AE /* ImportRecoveryPhrasePage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportRecoveryPhrasePage.swift; sourceTree = ""; };
+ CD1957DB2FB37AD40072A9AE /* MykeyPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MykeyPage.swift; sourceTree = ""; };
+ CD1957DC2FB37AD40072A9AE /* NewRecoveryPhrasePage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewRecoveryPhrasePage.swift; sourceTree = ""; };
+ CD1957DD2FB37AD40072A9AE /* OnboardingPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingPage.swift; sourceTree = ""; };
+ CD1957DE2FB37AD40072A9AE /* PortfolioBalancePage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PortfolioBalancePage.swift; sourceTree = ""; };
+ CD1957DF2FB37AD40072A9AE /* SelectCurrencyPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectCurrencyPage.swift; sourceTree = ""; };
+ CD1957E02FB37AD40072A9AE /* SelectKeyToDepositPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectKeyToDepositPage.swift; sourceTree = ""; };
+ CD1957E12FB37AD40072A9AE /* SellPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SellPage.swift; sourceTree = ""; };
+ CD1957E22FB37AD40072A9AE /* SwapPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwapPage.swift; sourceTree = ""; };
+ CD1957E32FB37AD40072A9AE /* VerifyYourPhrasePage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerifyYourPhrasePage.swift; sourceTree = ""; };
+ CD1957E52FB37AD40072A9AE /* Test591_OnboardingCreateWallet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Test591_OnboardingCreateWallet.swift; sourceTree = ""; };
+ CD1957E62FB37AD40072A9AE /* Test592_ImportWalletRecoveryPhrase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Test592_ImportWalletRecoveryPhrase.swift; sourceTree = ""; };
+ CD1957E72FB37AD40072A9AE /* Test593_BTCConfirmPaymentScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Test593_BTCConfirmPaymentScreen.swift; sourceTree = ""; };
+ CD1957E82FB37AD40072A9AE /* Test594_BasicSwapBTC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Test594_BasicSwapBTC.swift; sourceTree = ""; };
+ CD1957E92FB37AD40072A9AE /* Test595_BasicSellBTC_.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Test595_BasicSellBTC_.swift; sourceTree = ""; };
+ CD1957EA2FB37AD40072A9AE /* Test596_BasicBuyBTC_.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Test596_BasicBuyBTC_.swift; sourceTree = ""; };
+ CD1957EB2FB37AD40072A9AE /* TestPlan.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = TestPlan.xctestplan; sourceTree = ""; };
+ CD9683A72FB70CC700192CFF /* AppFlows.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppFlows.swift; sourceTree = ""; };
+ CDF085072FACD8A500CA4C1B /* BitPayAppUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BitPayAppUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+ E30DDDCF29E5E048D75CCF1A /* BitPayApp/BundleHash.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BitPayApp/BundleHash.swift; sourceTree = ""; };
E639E0380D05420CD0D64119 /* Pods-BitPayApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BitPayApp.release.xcconfig"; path = "Target Support Files/Pods-BitPayApp/Pods-BitPayApp.release.xcconfig"; sourceTree = ""; };
EC0FDD692CC92E90009E1B35 /* InAppMessageModule.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = InAppMessageModule.mm; sourceTree = ""; };
ECCB92762860B25D00F9CF8B /* SilentPushEvent.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SilentPushEvent.mm; sourceTree = ""; };
@@ -81,6 +140,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ CDF085042FACD8A500CA4C1B /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
@@ -107,7 +173,7 @@
5BEA4BEE2838400700AB2121 /* RCTPaymentPass.h */,
5BEA4BEF2838402700AB2121 /* RCTPaymentPass.mm */,
5D5D40A7E7B5265D652187F9 /* PrivacyInfo.xcprivacy */,
- E30DDDCF29E5E048D75CCF1A /* BundleHash.swift */,
+ E30DDDCF29E5E048D75CCF1A /* BitPayApp/BundleHash.swift */,
);
name = BitPayApp;
sourceTree = "";
@@ -158,10 +224,12 @@
children = (
13B07FAE1A68108700A75B9A /* BitPayApp */,
832341AE1AAA6A7D00B99B32 /* Libraries */,
+ CDF085082FACD8A500CA4C1B /* BitPayAppUITests */,
83CBBA001A601CBA00E9B192 /* Products */,
2D16E6871FA4F8E400B85C8A /* Frameworks */,
7950FCCDC90E6306EDD77BCD /* Pods */,
3A6F88D6BF7C4F7A8D93264A /* Resources */,
+ CDF085022FACD87900CA4C1B /* Recovered References */,
);
indentWidth = 2;
sourceTree = "";
@@ -172,10 +240,82 @@
isa = PBXGroup;
children = (
13B07F961A680F5B00A75B9A /* BitPayApp.app */,
+ CDF085072FACD8A500CA4C1B /* BitPayAppUITests.xctest */,
);
name = Products;
sourceTree = "";
};
+ CD1957D42FB37AD40072A9AE /* AllureReport */ = {
+ isa = PBXGroup;
+ children = (
+ CD1957D32FB37AD40072A9AE /* AllureXCTestSupport.swift */,
+ );
+ path = AllureReport;
+ sourceTree = "";
+ };
+ CD1957E42FB37AD40072A9AE /* pages */ = {
+ isa = PBXGroup;
+ children = (
+ CD1957D52FB37AD40072A9AE /* AddCryptoOptionPage.swift */,
+ CD1957D62FB37AD40072A9AE /* BuyPage.swift */,
+ CD1957D72FB37AD40072A9AE /* ConfirmPaymentPage.swift */,
+ CD1957D82FB37AD40072A9AE /* EnterAmountPage.swift */,
+ CD1957D92FB37AD40072A9AE /* HomePage.swift */,
+ CD1957DA2FB37AD40072A9AE /* ImportRecoveryPhrasePage.swift */,
+ CD1957DB2FB37AD40072A9AE /* MykeyPage.swift */,
+ CD1957DC2FB37AD40072A9AE /* NewRecoveryPhrasePage.swift */,
+ CD1957DD2FB37AD40072A9AE /* OnboardingPage.swift */,
+ CD1957DE2FB37AD40072A9AE /* PortfolioBalancePage.swift */,
+ CD1957DF2FB37AD40072A9AE /* SelectCurrencyPage.swift */,
+ CD1957E02FB37AD40072A9AE /* SelectKeyToDepositPage.swift */,
+ CD1957E12FB37AD40072A9AE /* SellPage.swift */,
+ CD1957E22FB37AD40072A9AE /* SwapPage.swift */,
+ CD1957E32FB37AD40072A9AE /* VerifyYourPhrasePage.swift */,
+ );
+ path = pages;
+ sourceTree = "";
+ };
+ CD1957EC2FB37AD40072A9AE /* tests */ = {
+ isa = PBXGroup;
+ children = (
+ CD1957E52FB37AD40072A9AE /* Test591_OnboardingCreateWallet.swift */,
+ CD1957E62FB37AD40072A9AE /* Test592_ImportWalletRecoveryPhrase.swift */,
+ CD1957E72FB37AD40072A9AE /* Test593_BTCConfirmPaymentScreen.swift */,
+ CD1957E82FB37AD40072A9AE /* Test594_BasicSwapBTC.swift */,
+ CD1957E92FB37AD40072A9AE /* Test595_BasicSellBTC_.swift */,
+ CD1957EA2FB37AD40072A9AE /* Test596_BasicBuyBTC_.swift */,
+ CD1957EB2FB37AD40072A9AE /* TestPlan.xctestplan */,
+ );
+ path = tests;
+ sourceTree = "";
+ };
+ CD96839F2FB70CAF00192CFF /* flows */ = {
+ isa = PBXGroup;
+ children = (
+ CD9683A72FB70CC700192CFF /* AppFlows.swift */,
+ );
+ path = flows;
+ sourceTree = "";
+ };
+ CDF085022FACD87900CA4C1B /* Recovered References */ = {
+ isa = PBXGroup;
+ children = (
+ 6D7C95E62E9007E200F0D481 /* RCTPushPermissionManager.mm */,
+ );
+ name = "Recovered References";
+ sourceTree = "";
+ };
+ CDF085082FACD8A500CA4C1B /* BitPayAppUITests */ = {
+ isa = PBXGroup;
+ children = (
+ CD1957D42FB37AD40072A9AE /* AllureReport */,
+ CD1957E42FB37AD40072A9AE /* pages */,
+ CD1957EC2FB37AD40072A9AE /* tests */,
+ CD96839F2FB70CAF00192CFF /* flows */,
+ );
+ path = BitPayAppUITests;
+ sourceTree = "";
+ };
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@@ -202,17 +342,40 @@
productReference = 13B07F961A680F5B00A75B9A /* BitPayApp.app */;
productType = "com.apple.product-type.application";
};
+ CDF085062FACD8A500CA4C1B /* BitPayAppUITests */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = CDF085112FACD8A500CA4C1B /* Build configuration list for PBXNativeTarget "BitPayAppUITests" */;
+ buildPhases = (
+ CDF085032FACD8A500CA4C1B /* Sources */,
+ CDF085042FACD8A500CA4C1B /* Frameworks */,
+ CDF085052FACD8A500CA4C1B /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ CDF0850E2FACD8A500CA4C1B /* PBXTargetDependency */,
+ );
+ name = BitPayAppUITests;
+ productName = BitPayAppUITests;
+ productReference = CDF085072FACD8A500CA4C1B /* BitPayAppUITests.xctest */;
+ productType = "com.apple.product-type.bundle.ui-testing";
+ };
/* End PBXNativeTarget section */
/* Begin PBXProject section */
83CBB9F71A601CBA00E9B192 /* Project object */ = {
isa = PBXProject;
attributes = {
+ LastSwiftUpdateCheck = 2600;
LastUpgradeCheck = 1210;
TargetAttributes = {
13B07F861A680F5B00A75B9A = {
LastSwiftMigration = 1240;
};
+ CDF085062FACD8A500CA4C1B = {
+ CreatedOnToolsVersion = 26.0.1;
+ TestTargetID = 13B07F861A680F5B00A75B9A;
+ };
};
};
buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "BitPayApp" */;
@@ -229,6 +392,7 @@
projectRoot = "";
targets = (
13B07F861A680F5B00A75B9A /* BitPayApp */,
+ CDF085062FACD8A500CA4C1B /* BitPayAppUITests */,
);
};
/* End PBXProject section */
@@ -252,6 +416,14 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ CDF085052FACD8A500CA4C1B /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ CD1958032FB37AD40072A9AE /* TestPlan.xctestplan in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
@@ -267,7 +439,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "set -e\n\nif [ -z \"$NODE_BINARY\" ]; then\n export NODE_BINARY=$(command -v node || true)\nfi\nif [ -z \"$NODE_BINARY\" ] || [ ! -x \"$NODE_BINARY\" ]; then\n echo \"error: NODE_BINARY not set and 'node' not found on PATH.\"\n exit 1\nfi\necho \"Using NODE_BINARY=$NODE_BINARY\"\n\nWITH_ENVIRONMENT=\"$REACT_NATIVE_PATH/scripts/xcode/with-environment.sh\"\nSENTRY_XCODE=\"../node_modules/@sentry/react-native/scripts/sentry-xcode.sh\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT $SENTRY_XCODE\"\n";
+ shellScript = "export SENTRY_DISABLE_AUTO_UPLOAD=true\n\nset -e\n\nif [ -z \"$NODE_BINARY\" ]; then\n export NODE_BINARY=$(command -v node || true)\nfi\nif [ -z \"$NODE_BINARY\" ] || [ ! -x \"$NODE_BINARY\" ]; then\n echo \"error: NODE_BINARY not set and 'node' not found on PATH.\"\n exit 1\nfi\necho \"Using NODE_BINARY=$NODE_BINARY\"\n\nWITH_ENVIRONMENT=\"$REACT_NATIVE_PATH/scripts/xcode/with-environment.sh\"\nSENTRY_XCODE=\"../node_modules/@sentry/react-native/scripts/sentry-xcode.sh\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT $SENTRY_XCODE\"\n";
};
3C1070649A3322DC1391852F /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
@@ -299,10 +471,14 @@
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-BitPayApp/Pods-BitPayApp-resources-${CONFIGURATION}-input-files.xcfilelist",
);
+ inputPaths = (
+ );
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-BitPayApp/Pods-BitPayApp-resources-${CONFIGURATION}-output-files.xcfilelist",
);
+ outputPaths = (
+ );
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-BitPayApp/Pods-BitPayApp-resources.sh\"\n";
@@ -335,10 +511,14 @@
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-BitPayApp/Pods-BitPayApp-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
+ inputPaths = (
+ );
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-BitPayApp/Pods-BitPayApp-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
+ outputPaths = (
+ );
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-BitPayApp/Pods-BitPayApp-frameworks.sh\"\n";
@@ -374,19 +554,56 @@
6D615C4929725FD70063DB8E /* RCTTimer.mm in Sources */,
ECCB92782860B25D00F9CF8B /* SilentPushEvent.mm in Sources */,
5B31AB5F2786515A002765E1 /* Dosh.swift in Sources */,
- 52C9714B9004BCE190EA469D /* BundleHash.swift in Sources */,
+ 52C9714B9004BCE190EA469D /* BitPayApp/BundleHash.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ CDF085032FACD8A500CA4C1B /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ CD1957ED2FB37AD40072A9AE /* Test595_BasicSellBTC_.swift in Sources */,
+ CD1957EE2FB37AD40072A9AE /* Test592_ImportWalletRecoveryPhrase.swift in Sources */,
+ CD1957EF2FB37AD40072A9AE /* MykeyPage.swift in Sources */,
+ CD1957F02FB37AD40072A9AE /* BuyPage.swift in Sources */,
+ CD1957F12FB37AD40072A9AE /* SellPage.swift in Sources */,
+ CD1957F22FB37AD40072A9AE /* Test591_OnboardingCreateWallet.swift in Sources */,
+ CD1957F32FB37AD40072A9AE /* Test596_BasicBuyBTC_.swift in Sources */,
+ CD9683A82FB70CCC00192CFF /* AppFlows.swift in Sources */,
+ CD1957F42FB37AD40072A9AE /* NewRecoveryPhrasePage.swift in Sources */,
+ CD1957F52FB37AD40072A9AE /* VerifyYourPhrasePage.swift in Sources */,
+ CD1957F62FB37AD40072A9AE /* ConfirmPaymentPage.swift in Sources */,
+ CD1957F72FB37AD40072A9AE /* AllureXCTestSupport.swift in Sources */,
+ CD1957F82FB37AD40072A9AE /* EnterAmountPage.swift in Sources */,
+ CD1957F92FB37AD40072A9AE /* Test594_BasicSwapBTC.swift in Sources */,
+ CD1957FA2FB37AD40072A9AE /* AddCryptoOptionPage.swift in Sources */,
+ CD1957FB2FB37AD40072A9AE /* OnboardingPage.swift in Sources */,
+ CD1957FC2FB37AD40072A9AE /* ImportRecoveryPhrasePage.swift in Sources */,
+ CD1957FD2FB37AD40072A9AE /* PortfolioBalancePage.swift in Sources */,
+ CD1957FE2FB37AD40072A9AE /* SwapPage.swift in Sources */,
+ CD1957FF2FB37AD40072A9AE /* Test593_BTCConfirmPaymentScreen.swift in Sources */,
+ CD1958002FB37AD40072A9AE /* SelectKeyToDepositPage.swift in Sources */,
+ CD1958012FB37AD40072A9AE /* SelectCurrencyPage.swift in Sources */,
+ CD1958022FB37AD40072A9AE /* HomePage.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
+/* Begin PBXTargetDependency section */
+ CDF0850E2FACD8A500CA4C1B /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 13B07F861A680F5B00A75B9A /* BitPayApp */;
+ targetProxy = CDF0850D2FACD8A500CA4C1B /* PBXContainerItemProxy */;
+ };
+/* End PBXTargetDependency section */
+
/* Begin XCBuildConfiguration section */
13B07F941A680F5B00A75B9A /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 4221E54904CA4996330CEB54 /* Pods-BitPayApp.debug.xcconfig */;
buildSettings = {
- ALLOWED_URL_PREFIXES = "ALLOWED-URL-PREFIXES-REPLACE-ME";
- ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)";
+ ALLOWED_URL_PREFIXES = "https://bitpay.com/,https://test.bitpay.com/,https://staging.bitpay.com/,https://bws.bitpay.com/,https://www.coinbase.com/,https://api.coinbase.com/,https://api.1inch.dev/swap/v5.2/,https://api.coingecko.com/api/v3/simple/token_price/,https://checkout.simplexcc.com/,https://sandbox.test-simplexcc.com,https://api.moonpay.com/,https://api.moonpay.dev/,https://api.zenledger.io/bitpay/wallets/,https://rest.iad-05.braze.com/,https://sdk.iad-05.braze.com/,https://cloudflare-eth.com/,https://sepolia.infura.io/v3/,https://polygon-rpc.com/,https://polygon-amoy.core.chainstack.com/,https://static.methodfi.com/,https://api.zenledger.io/,https://api.blockcypher.com/v1/,https://api.fullstack.cash/v5/,https://deep-index.moralis.io/api/v2.2/,https://api.bitcore.io/api,https://api-eth.bitcore.io/api,https://api-matic.bitcore.io/api,https://api-arb.bitcore.io/api,https://api-base.bitcore.io/api,https://api-op.bitcore.io/api,https://api-xrp.bitcore.io/api,https://api-sol.bitcore.io/api,https://portal.polygon.technology/bridge,https://bridge.arbitrum.io/,https://bridge.base.org/,https://app.optimism.io/bridge/,https://mainnet.optimism.io/,https://arb1.arbitrum.io/rpc,https://mainnet.base.org/,https://goerli.optimism.io,https://goerli-rollup.arbitrum.io/rpc,https://goerli.base.org,https://verify.walletconnect.com/,https://verify.walletconnect.org/,https://api.etherscan.io/v2/api,https://solana-gateway.moralis.io/,https://tokens-data.1inch.io/images/501/";
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = NO;
CLANG_ENABLE_MODULES = YES;
@@ -431,8 +648,7 @@
isa = XCBuildConfiguration;
baseConfigurationReference = E639E0380D05420CD0D64119 /* Pods-BitPayApp.release.xcconfig */;
buildSettings = {
- ALLOWED_URL_PREFIXES = "ALLOWED-URL-PREFIXES-REPLACE-ME";
- ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)";
+ ALLOWED_URL_PREFIXES = "https://bitpay.com/,https://test.bitpay.com/,https://staging.bitpay.com/,https://bws.bitpay.com/,https://www.coinbase.com/,https://api.coinbase.com/,https://api.1inch.dev/swap/v5.2/,https://api.coingecko.com/api/v3/simple/token_price/,https://checkout.simplexcc.com/,https://sandbox.test-simplexcc.com,https://api.moonpay.com/,https://api.moonpay.dev/,https://api.zenledger.io/bitpay/wallets/,https://rest.iad-05.braze.com/,https://sdk.iad-05.braze.com/,https://cloudflare-eth.com/,https://sepolia.infura.io/v3/,https://polygon-rpc.com/,https://polygon-amoy.core.chainstack.com/,https://static.methodfi.com/,https://api.zenledger.io/,https://api.blockcypher.com/v1/,https://api.fullstack.cash/v5/,https://deep-index.moralis.io/api/v2.2/,https://api.bitcore.io/api,https://api-eth.bitcore.io/api,https://api-matic.bitcore.io/api,https://api-arb.bitcore.io/api,https://api-base.bitcore.io/api,https://api-op.bitcore.io/api,https://api-xrp.bitcore.io/api,https://api-sol.bitcore.io/api,https://portal.polygon.technology/bridge,https://bridge.arbitrum.io/,https://bridge.base.org/,https://app.optimism.io/bridge/,https://mainnet.optimism.io/,https://arb1.arbitrum.io/rpc,https://mainnet.base.org/,https://goerli.optimism.io,https://goerli-rollup.arbitrum.io/rpc,https://goerli.base.org,https://verify.walletconnect.com/,https://verify.walletconnect.org/,https://api.etherscan.io/v2/api,https://solana-gateway.moralis.io/,https://tokens-data.1inch.io/images/501/";
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = NO;
CLANG_ENABLE_MODULES = YES;
@@ -649,6 +865,81 @@
};
name = Release;
};
+ CDF0850F2FACD8A500CA4C1B /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu17;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GENERATE_INFOPLIST_FILE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 26.0;
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+ MARKETING_VERSION = 1.0;
+ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+ MTL_FAST_MATH = YES;
+ PRODUCT_BUNDLE_IDENTIFIER = com.test.appium.BitPayAppUITests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SKIP_INSTALL = NO;
+ STRING_CATALOG_GENERATE_SYMBOLS = NO;
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
+ SWIFT_APPROACHABLE_CONCURRENCY = YES;
+ SWIFT_EMIT_LOC_STRINGS = NO;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ TEST_TARGET_NAME = BitPayApp;
+ };
+ name = Debug;
+ };
+ CDF085102FACD8A500CA4C1B /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CODE_SIGN_STYLE = Automatic;
+ COPY_PHASE_STRIP = NO;
+ CURRENT_PROJECT_VERSION = 1;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu17;
+ GENERATE_INFOPLIST_FILE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 26.0;
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+ MARKETING_VERSION = 1.0;
+ MTL_FAST_MATH = YES;
+ PRODUCT_BUNDLE_IDENTIFIER = com.test.appium.BitPayAppUITests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SKIP_INSTALL = NO;
+ STRING_CATALOG_GENERATE_SYMBOLS = NO;
+ SWIFT_APPROACHABLE_CONCURRENCY = YES;
+ SWIFT_COMPILATION_MODE = wholemodule;
+ SWIFT_EMIT_LOC_STRINGS = NO;
+ SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ TEST_TARGET_NAME = BitPayApp;
+ };
+ name = Release;
+ };
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
@@ -670,6 +961,15 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
+ CDF085112FACD8A500CA4C1B /* Build configuration list for PBXNativeTarget "BitPayAppUITests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ CDF0850F2FACD8A500CA4C1B /* Debug */,
+ CDF085102FACD8A500CA4C1B /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
/* End XCConfigurationList section */
};
rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */;
diff --git a/ios/BitPayApp.xcodeproj/xcshareddata/xcschemes/BitPayApp.xcscheme b/ios/BitPayApp.xcodeproj/xcshareddata/xcschemes/BitPayApp.xcscheme
index 3874813c99..92d73acdf4 100644
--- a/ios/BitPayApp.xcodeproj/xcshareddata/xcschemes/BitPayApp.xcscheme
+++ b/ios/BitPayApp.xcodeproj/xcshareddata/xcschemes/BitPayApp.xcscheme
@@ -26,7 +26,9 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
- shouldUseLaunchSchemeArgsEnv = "YES">
+ shouldUseLaunchSchemeArgsEnv = "YES"
+ systemAttachmentLifetime = "keepAlways"
+ userAttachmentLifetime = "keepAlways">
@@ -38,6 +40,17 @@
ReferencedContainer = "container:BitPayApp.xcodeproj">
+
+
+
+
:` convention.
+ static func addLabel(_ name: String, value: String) {
+ XCTContext.runActivity(named: "allure.label.\(name):\(value)") { _ in }
+ }
+
+ /// Sets a custom test name visible in the report.
+ static func setDisplayName(_ name: String) {
+ XCTContext.runActivity(named: "allure.name:\(name)") { _ in }
+ }
+
+ /// Adds a link to an issue tracker entry.
+ static func addIssue(_ title: String, url: String) {
+ XCTContext.runActivity(named: "allure.issue.\(title):\(url)") { _ in }
+ }
+
+ /// Adds a generic hyperlink label.
+ static func addLink(_ title: String, url: String) {
+ XCTContext.runActivity(named: "allure.link.\(title):\(url)") { _ in }
+ }
+
+ /// Adds a plain description to the test.
+ static func addDescription(_ description: String) {
+ XCTContext.runActivity(named: "allure.description:\(description)") { _ in }
+ }
+
+ /// Wraps a code block in an activity that appears as a step in Allure.
+ @discardableResult
+ static func step(_ name: String, block: () throws -> T) rethrows -> T {
+ try XCTContext.runActivity(named: name) { _ in try block() }
+ }
+}
+
+extension XCTestCase {
+ /// Attaches a full-screen capture to the current test. Lifetime is `.keepAlways`
+ /// so it remains available in the `.xcresult` bundle for Allure to render.
+ func allureAttachScreenshot(name: String = "Screenshot") {
+ let screenshot = XCUIScreen.main.screenshot()
+ let attachment = XCTAttachment(screenshot: screenshot)
+ attachment.name = name
+ attachment.lifetime = .keepAlways
+ add(attachment)
+ }
+
+ /// Attaches a screenshot of the supplied application.
+ func allureAttachAppScreenshot(app: XCUIApplication, name: String = "App Screenshot") {
+ let attachment = XCTAttachment(screenshot: app.screenshot())
+ attachment.name = name
+ attachment.lifetime = .keepAlways
+ add(attachment)
+ }
+
+ /// Captures failure-time artifacts if the current test did not succeed.
+ func allureCaptureFailureArtifacts(app: XCUIApplication?, name: String = "Failure Screenshot") {
+ guard let app else { return }
+ let attachment = XCTAttachment(screenshot: app.screenshot())
+ attachment.name = name
+ attachment.lifetime = .keepAlways
+ add(attachment)
+ }
+}
+
diff --git a/ios/BitPayAppUITests/flows/AppFlows.swift b/ios/BitPayAppUITests/flows/AppFlows.swift
new file mode 100644
index 0000000000..c13c28ac1b
--- /dev/null
+++ b/ios/BitPayAppUITests/flows/AppFlows.swift
@@ -0,0 +1,39 @@
+//
+// AppFlows.swift
+// BitPayApp
+//
+// Created by vinoth vasu on 15/05/26.
+//
+
+import XCTest
+
+final class AppFlows {
+
+ static func completeOnboardingIfRequired(app: XCUIApplication) {
+
+ let onboardingPage = OnboardingPage(app: app)
+
+ if onboardingPage.isContinueWithoutAccountButtonDisplayed() {
+
+ onboardingPage.tapContinuewithoutAnAccount()
+ onboardingPage.skipOnboarding()
+ onboardingPage.skipOnboarding()
+ onboardingPage.createWallet()
+
+ guard onboardingPage.isBackupKeyLabelDisplayed() else {
+ XCTFail("Backup key page did not appear")
+ return
+ }
+
+ onboardingPage.skipBackup()
+ onboardingPage.tapLater()
+ onboardingPage.acceptTerms()
+ onboardingPage.tapAgreeAndContinueButton()
+
+ XCTAssertTrue(
+ onboardingPage.isYourPortfolioBalanceTextDisplayed(),
+ "Home screen did not load"
+ )
+ }
+ }
+}
diff --git a/ios/BitPayAppUITests/pages/AddCryptoOptionPage.swift b/ios/BitPayAppUITests/pages/AddCryptoOptionPage.swift
new file mode 100644
index 0000000000..4b26128fee
--- /dev/null
+++ b/ios/BitPayAppUITests/pages/AddCryptoOptionPage.swift
@@ -0,0 +1,34 @@
+import XCTest
+
+class AddCryptoOptionPage {
+
+ let app: XCUIApplication
+
+ init(app: XCUIApplication) {
+ self.app = app
+ }
+
+ // MARK: - Elements
+ var selectAnOptionText: XCUIElement {
+ app.descendants(matching: .any).element(
+ matching: NSPredicate(format: "label == 'Select an Option'")
+ ).firstMatch
+ }
+
+ var importKey: XCUIElement {
+ app.descendants(matching: .any).element(
+ matching: NSPredicate(format: "label == 'Import Key'")
+ ).firstMatch
+ }
+
+ // MARK: - Actions
+
+ func isSelectAnOptionTitleDisplayed(timeout: TimeInterval = 30) -> Bool {
+ return selectAnOptionText.waitForExistence(timeout: timeout)
+ }
+
+ func tapImportKey() {
+ importKey.tap()
+ }
+
+}
diff --git a/ios/BitPayAppUITests/pages/BuyPage.swift b/ios/BitPayAppUITests/pages/BuyPage.swift
new file mode 100644
index 0000000000..a83276284b
--- /dev/null
+++ b/ios/BitPayAppUITests/pages/BuyPage.swift
@@ -0,0 +1,34 @@
+import XCTest
+
+class BuyPage {
+
+ let app: XCUIApplication
+
+ init(app: XCUIApplication) {
+ self.app = app
+ }
+
+ // MARK: - Elements
+ var buyTitle: XCUIElement {
+ app.staticTexts["Buy"].firstMatch
+ }
+
+ var bitcoin: XCUIElement {
+ app.otherElements.matching(
+ NSPredicate(format: "label == 'Bitcoin'")
+ ).firstMatch
+ }
+
+
+ // MARK: - Validations
+
+ func isBuyPageTitleDisplayed(timeout: TimeInterval = 180) -> Bool {
+ return buyTitle.waitForExistence(timeout: timeout)
+ }
+
+ func tapBitcoin() {
+ bitcoin.tap()
+ }
+
+
+}
diff --git a/ios/BitPayAppUITests/pages/ConfirmPaymentPage.swift b/ios/BitPayAppUITests/pages/ConfirmPaymentPage.swift
new file mode 100644
index 0000000000..fd82fc0aac
--- /dev/null
+++ b/ios/BitPayAppUITests/pages/ConfirmPaymentPage.swift
@@ -0,0 +1,45 @@
+
+//
+// OnboardingPage.swift
+// BitPayApp
+//
+// Created by vinoth vasu on 16/03/26.
+//
+
+import XCTest
+
+class ConfirmPaymentPage {
+
+ let app: XCUIApplication
+
+ init(app: XCUIApplication) {
+ self.app = app
+ }
+
+ // MARK: - Elements
+
+ var confirmPaymentText: XCUIElement {
+ app.descendants(matching: .any).element(
+ matching: NSPredicate(format: "label == 'Confirm Payment'")
+ ).firstMatch
+ }
+
+ var summaryText: XCUIElement {
+ app.descendants(matching: .any).element(
+ matching: NSPredicate(format: "label == 'SUMMARY'")
+ ).firstMatch
+ }
+
+
+ // MARK: - Actions
+
+ func isConfirmPaymentTitleDisplayed(timeout: TimeInterval = 180) -> Bool {
+ return confirmPaymentText.waitForExistence(timeout: timeout)
+ }
+
+ func isSummaryTextDisplayed(timeout: TimeInterval = 5) -> Bool {
+ return summaryText.waitForExistence(timeout: timeout)
+ }
+
+
+}
diff --git a/ios/BitPayAppUITests/pages/EnterAmountPage.swift b/ios/BitPayAppUITests/pages/EnterAmountPage.swift
new file mode 100644
index 0000000000..ed597762ad
--- /dev/null
+++ b/ios/BitPayAppUITests/pages/EnterAmountPage.swift
@@ -0,0 +1,86 @@
+
+//
+// OnboardingPage.swift
+// BitPayApp
+//
+// Created by vinoth vasu on 16/03/26.
+//
+
+import XCTest
+
+class EnterAmountPage {
+
+ let app: XCUIApplication
+
+ init(app: XCUIApplication) {
+ self.app = app
+ }
+
+ // MARK: - Elements
+ var amountField: XCUIElement {
+ app.staticTexts.matching(identifier: "0").firstMatch
+ }
+
+ var continueButton: XCUIElement {
+ app.descendants(matching: .any).element(
+ matching: NSPredicate(format: "label CONTAINS 'Continue'")
+ ).firstMatch
+ }
+
+ // MARK: - Actions
+ func enterAmount(amount: String = "0") {
+ for char in amount {
+ let key = app.staticTexts[String(char)].firstMatch
+ XCTAssertTrue(key.waitForExistence(timeout: 5))
+ key.tap()
+ }
+ }
+
+ func tapContinue() {
+ continueButton.tap()
+ }
+
+ /**
+ Backspace key does not expose a unique accessibility identifier.
+ Calculate its position dynamically using the relative spacing
+ between the keypad keys "6" and "9".
+ */
+ func tapBackspace(count: Int = 1) {
+
+ let sixKey = app.staticTexts["6"].firstMatch
+ let nineKey = app.staticTexts["9"].firstMatch
+
+ XCTAssertTrue(
+ sixKey.waitForExistence(timeout: 5),
+ "6 key not found"
+ )
+
+ XCTAssertTrue(
+ nineKey.waitForExistence(timeout: 5),
+ "9 key not found"
+ )
+
+ let sixFrame = sixKey.frame
+ let nineFrame = nineKey.frame
+
+ let rowHeight = abs(nineFrame.midY - sixFrame.midY)
+
+ let backspaceX = nineFrame.midX
+ let backspaceY = nineFrame.midY + rowHeight
+
+ let backspaceCoordinate = app.coordinate(
+ withNormalizedOffset: CGVector(dx: 0, dy: 0)
+ ).withOffset(
+ CGVector(
+ dx: backspaceX,
+ dy: backspaceY
+ )
+ )
+
+ for _ in 0.. Bool {
+ return backupKeyLabel.waitForExistence(timeout: timeout)
+ }
+
+ func tapLater() {
+ laterButton.tap()
+ }
+
+ func acceptTerms() {
+ checkbox1.tap()
+ checkbox2.tap()
+ checkbox3.tap()
+ }
+
+ func tapAgreeAndContinueButton() {
+ agreeAndContinueButton.tap()
+ }
+
+ func isYourPortfolioBalanceTextDisplayed(timeout: TimeInterval = 25) -> Bool {
+ return yourPortfolioBalanceText.waitForExistence(timeout: timeout)
+ }
+
+ func swipeOnboarding() {
+ onboardingScrollView.swipeRight()
+ }
+
+ func tapGotIt() {
+ gotItButton.tap()
+ }
+
+ func tapBuyOption() {
+ buyButton.tap()
+ }
+
+ func tapBackUpKey() {
+ backUpKey.tap()
+ }
+
+ func tapAddCryptoButton() {
+ addCryptoButton.tap()
+ }
+
+}
diff --git a/ios/BitPayAppUITests/pages/ImportRecoveryPhrasePage.swift b/ios/BitPayAppUITests/pages/ImportRecoveryPhrasePage.swift
new file mode 100644
index 0000000000..1ea89287a5
--- /dev/null
+++ b/ios/BitPayAppUITests/pages/ImportRecoveryPhrasePage.swift
@@ -0,0 +1,39 @@
+import XCTest
+
+class ImportRecoveryPhrasePage {
+
+ let app: XCUIApplication
+
+ init(app: XCUIApplication) {
+ self.app = app
+ }
+
+ // MARK: - Elements
+
+ var inputRecoveryPhrase: XCUIElement {
+ app.descendants(matching: .any)
+ .matching(NSPredicate(format: "label == 'Enter recovery phrase'"))
+ .firstMatch
+ }
+
+ var importWalletButton: XCUIElement {
+ app.descendants(matching: .any)
+ .matching(NSPredicate(format: "label == 'Import wallet'"))
+ .firstMatch
+ }
+
+ // MARK: - Actions
+
+ func isImportScreenDisplayed(timeout: TimeInterval = 5) -> Bool {
+ return inputRecoveryPhrase.waitForExistence(timeout: timeout)
+ }
+
+ func enterRecoveryPhrase(_ phrase: String) {
+ inputRecoveryPhrase.tap()
+ inputRecoveryPhrase.typeText(phrase)
+ }
+
+ func tapImportWallet() {
+ importWalletButton.tap()
+ }
+}
diff --git a/ios/BitPayAppUITests/pages/MykeyPage.swift b/ios/BitPayAppUITests/pages/MykeyPage.swift
new file mode 100644
index 0000000000..1fb5546839
--- /dev/null
+++ b/ios/BitPayAppUITests/pages/MykeyPage.swift
@@ -0,0 +1,45 @@
+import XCTest
+
+class MyKeyPage {
+
+ let app: XCUIApplication
+
+ init(app: XCUIApplication) {
+ self.app = app
+ }
+
+ // MARK: - Elements
+
+ var myKeyTab: XCUIElement {
+ app.otherElements.matching(
+ NSPredicate(format: "label == 'My Key'")
+ ).firstMatch
+ }
+
+ var myWalletsText: XCUIElement {
+ app.staticTexts.matching(
+ NSPredicate(format: "label == 'My Wallets'")
+ ).firstMatch
+ }
+
+ var bitcoinWallet: XCUIElement {
+ app.descendants(matching: .any).matching(
+ NSPredicate(format: "label CONTAINS[c] %@", "Bitcoin")
+ ).firstMatch
+ }
+
+ // MARK: - Validations
+
+ func isMyKeyDisplayed(timeout: TimeInterval = 900) -> Bool {
+ return myKeyTab.waitForExistence(timeout: timeout)
+ }
+
+ func isMyWalletsDisplayed(timeout: TimeInterval = 5) -> Bool {
+ return myWalletsText.waitForExistence(timeout: timeout)
+ }
+
+ func isBitcoinBTCWalletDisplayed(timeout: TimeInterval = 300) -> Bool {
+ return bitcoinWallet.waitForExistence(timeout: timeout)
+ }
+
+}
diff --git a/ios/BitPayAppUITests/pages/NewRecoveryPhrasePage.swift b/ios/BitPayAppUITests/pages/NewRecoveryPhrasePage.swift
new file mode 100644
index 0000000000..37ded1f5ba
--- /dev/null
+++ b/ios/BitPayAppUITests/pages/NewRecoveryPhrasePage.swift
@@ -0,0 +1,89 @@
+import XCTest
+
+class NewRecoveryPhrasePage {
+
+ let app: XCUIApplication
+
+ init(app: XCUIApplication) {
+ self.app = app
+ }
+
+ // MARK: - Elements
+
+ var instructionText: XCUIElement {
+ app.staticTexts.matching(
+ NSPredicate(format: "label CONTAINS 'recovery phrase'")
+ ).firstMatch
+ }
+
+ var textFields: [XCUIElement] {
+ app.textFields.allElementsBoundByIndex
+ }
+
+ var confirmButton: XCUIElement {
+ app.otherElements["Confirm"].firstMatch
+ }
+
+ var okButton: XCUIElement {
+ app.staticTexts["OK"].firstMatch
+ }
+
+ // MARK: - Actions
+
+ func isVerifyScreenDisplayed(timeout: TimeInterval = 10) -> Bool {
+ return instructionText.waitForExistence(timeout: timeout)
+ }
+
+ func tapConfirm() {
+ XCTAssertTrue(confirmButton.waitForExistence(timeout: 5))
+ confirmButton.tap()
+ }
+
+ func tapOK() {
+ XCTAssertTrue(okButton.waitForExistence(timeout: 5))
+ okButton.tap()
+ }
+
+ // MARK: - Business Logic
+
+ /// Extract indexes dynamically (e.g. 11, 9, 12)
+ func getRequiredIndexes() -> [Int] {
+
+ XCTAssertTrue(instructionText.waitForExistence(timeout: 5))
+
+ let instruction = instructionText.label
+
+ return extractNumbers(from: instruction).compactMap { Int($0) }
+ }
+
+ /// Enter words dynamically
+ func enterRecoveryPhrase(words: [String]) {
+
+ let indexes = getRequiredIndexes()
+
+ for (i, index) in indexes.enumerated() {
+ let word = words[index - 1]
+
+ let field = textFields[i]
+ XCTAssertTrue(field.waitForExistence(timeout: 5))
+
+ field.tap()
+ field.typeText(word)
+ }
+
+ NSLog("Entered words for indexes \(indexes)")
+ }
+
+ func extractNumbers(from text: String) -> [String] {
+ do {
+ let regex = try NSRegularExpression(pattern: "\\d+")
+ let results = regex.matches(
+ in: text,
+ range: NSRange(text.startIndex..., in: text)
+ )
+ return results.map { String(text[Range($0.range, in: text)!]) }
+ } catch {
+ return []
+ }
+ }
+}
diff --git a/ios/BitPayAppUITests/pages/OnboardingPage.swift b/ios/BitPayAppUITests/pages/OnboardingPage.swift
new file mode 100644
index 0000000000..b59d1cdabd
--- /dev/null
+++ b/ios/BitPayAppUITests/pages/OnboardingPage.swift
@@ -0,0 +1,176 @@
+//
+// OnboardingPage.swift
+// BitPayApp
+//
+// Created by vinoth vasu on 10/03/26.
+//
+
+import XCTest
+
+class OnboardingPage {
+
+ let app: XCUIApplication
+
+ init(app: XCUIApplication) {
+ self.app = app
+ }
+
+ // MARK: - Elements
+
+ var continueWithoutAnAccountButton: XCUIElement {
+ app.descendants(matching: .any)
+ .matching(NSPredicate(format: "label == 'Continue without an account'"))
+ .firstMatch
+ }
+
+ var skipButton: XCUIElement {
+ app.descendants(matching: .any).element(
+ matching: NSPredicate(format: "label == 'Skip'")
+ ).firstMatch
+ }
+
+ var skipBackupButton: XCUIElement {
+ app.descendants(matching: .any).element(
+ matching: NSPredicate(format: "label == 'Skip backup'")
+ ).firstMatch
+ }
+
+ var createKeyButton: XCUIElement {
+ app.descendants(matching: .any).element(
+ matching: NSPredicate(format: "label == 'Create a key'")
+ ).firstMatch
+ }
+
+ var alreadyHaveWalletKeyButton: XCUIElement {
+ app.descendants(matching: .any).element(
+ matching: NSPredicate(format: "label == 'I already have a key'")
+ ).firstMatch
+ }
+
+ var backupKeyLabel: XCUIElement {
+ app.staticTexts["Would you like to backup your key?"]
+ }
+
+ var laterButton: XCUIElement {
+ app.descendants(matching: .any).element(
+ matching: NSPredicate(format: "label == 'LATER'")
+ ).firstMatch
+ }
+
+ var gotItButton: XCUIElement {
+ app.staticTexts["GOT IT"]
+ }
+
+ var checkbox1: XCUIElement {
+ app.descendants(matching: .any).element(
+ matching: NSPredicate(
+ format:
+ "label CONTAINS 'Checkbox, My funds are held and controlled on this device'"
+ )
+ ).firstMatch
+ }
+
+ var checkbox2: XCUIElement {
+ app.descendants(matching: .any).element(
+ matching: NSPredicate(
+ format:
+ "label CONTAINS 'Checkbox, BitPay can never recover my funds for me'"
+ )
+ ).firstMatch
+ }
+
+ var checkbox3: XCUIElement {
+ app.descendants(matching: .any).element(
+ matching: NSPredicate(
+ format:
+ "label CONTAINS 'Checkbox, I have read, understood and accepted the Wallet Terms of Use'"
+ )
+ ).firstMatch
+ }
+
+ var agreeAndContinueButton: XCUIElement {
+ app.descendants(matching: .any).element(
+ matching: NSPredicate(format: "label == 'Agree and continue'")
+ ).firstMatch
+ }
+
+ var yourPortfolioBalanceText: XCUIElement {
+ app.descendants(matching: .any).element(
+ matching: NSPredicate(format: "label == 'Portfolio balance info'")
+ ).firstMatch
+ }
+
+ var onboardingScrollView: XCUIElement {
+ app.scrollViews.firstMatch
+ }
+
+ // MARK: - Actions
+
+ func handleTrackingPermissionIfDisplayed(timeout: TimeInterval = 30) {
+ let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")
+ let askAppNotToTrackButton = springboard.buttons["Ask App Not to Track"]
+
+ if askAppNotToTrackButton.waitForExistence(timeout: timeout) {
+ askAppNotToTrackButton.tap()
+ }
+ }
+
+ func isContinueWithoutAccountButtonDisplayed(timeout: TimeInterval = 15) -> Bool {
+ return continueWithoutAnAccountButton.waitForExistence(timeout: timeout)
+ }
+
+ func tapContinuewithoutAnAccount() {
+ continueWithoutAnAccountButton.tap()
+ }
+
+ func skipOnboarding() {
+ skipButton.tap()
+ }
+
+ func skipBackup() {
+ skipBackupButton.tap()
+ }
+
+ func createWallet() {
+ createKeyButton.tap()
+ }
+
+ func alreadyHaveWalletKey() {
+ alreadyHaveWalletKeyButton.tap()
+ }
+
+ func isBackupKeyLabelDisplayed(timeout: TimeInterval = 120) -> Bool {
+ return backupKeyLabel.waitForExistence(timeout: timeout)
+ }
+
+ func tapLater() {
+ laterButton.tap()
+ }
+
+ func acceptTerms() {
+ XCTAssertTrue(checkbox1.waitForExistence(timeout: 10))
+ checkbox1.tap()
+ checkbox2.tap()
+
+ let coordinate = checkbox3.coordinate(
+ withNormalizedOffset: CGVector(dx: 0.1, dy: 0.5)
+ )
+ coordinate.tap()
+ }
+
+ func tapAgreeAndContinueButton() {
+ agreeAndContinueButton.tap()
+ }
+
+ func isYourPortfolioBalanceTextDisplayed(timeout: TimeInterval = 25) -> Bool {
+ return yourPortfolioBalanceText.waitForExistence(timeout: timeout)
+ }
+
+ func swipeOnboarding() {
+ onboardingScrollView.swipeRight()
+ }
+
+ func tapGotIt() {
+ gotItButton.tap()
+ }
+}
diff --git a/ios/BitPayAppUITests/pages/PortfolioBalancePage.swift b/ios/BitPayAppUITests/pages/PortfolioBalancePage.swift
new file mode 100644
index 0000000000..36e4df842e
--- /dev/null
+++ b/ios/BitPayAppUITests/pages/PortfolioBalancePage.swift
@@ -0,0 +1,157 @@
+
+//
+// OnboardingPage.swift
+// BitPayApp
+//
+// Created by vinoth vasu on 16/03/26.
+//
+
+import XCTest
+
+class PortfolioBalancePage {
+
+ let app: XCUIApplication
+
+ init(app: XCUIApplication) {
+ self.app = app
+ }
+
+ // MARK: - Elements
+
+ var yourPortfolioBalanceText: XCUIElement {
+ app.otherElements["Portfolio Balance"].firstMatch
+ }
+
+ var addCryptoButton: XCUIElement {
+ app.descendants(matching: .any).element(
+ matching: NSPredicate(format: "label == 'Add crypto wallet'")
+ ).firstMatch
+ }
+
+ var buyButton: XCUIElement {
+ app.descendants(matching: .any)
+ .matching(identifier: "buy-button")
+ .firstMatch
+ }
+
+ var sellButton: XCUIElement {
+ app.descendants(matching: .any)
+ .matching(identifier: "sell-button")
+ .firstMatch
+ }
+
+ var sendButton: XCUIElement {
+ app.descendants(matching: .any)
+ .matching(identifier: "send-button")
+ .firstMatch
+ }
+
+ var swapButton: XCUIElement {
+ app.descendants(matching: .any)
+ .matching(identifier: "swap-button")
+ .firstMatch
+ }
+
+ // MARK: - Actions
+
+ func isYourPortfolioBalanceTextDisplayed(timeout: TimeInterval = 25) -> Bool {
+ return yourPortfolioBalanceText.waitForExistence(timeout: timeout)
+ }
+
+// func tapBuyButton() {
+// buyButton.tap()
+// }
+
+ func tapBuyButton(timeout: TimeInterval = 900) {
+ XCTAssertTrue(
+ buyButton.waitForExistence(timeout: timeout),
+ "Buy button did not appear within \(timeout) seconds"
+ )
+ buyButton.tap()
+ }
+
+// func tapSellButton() {
+// sellButton.tap()
+// }
+
+ func tapSellButton(timeout: TimeInterval = 900) {
+ XCTAssertTrue(
+ sellButton.waitForExistence(timeout: timeout),
+ "Sell button did not appear within \(timeout) seconds"
+ )
+ sellButton.tap()
+ }
+
+// func tapAddCryptoButton() {
+// addCryptoButton.tap()
+// }
+
+ func tapAddCryptoButton() {
+
+// let selectAnOptionPage = AddCryptoOptionPage(app: app)
+//
+// for attempt in 1...5 {
+//
+ addCryptoButton.tap()
+
+// if selectAnOptionPage.isSelectAnOptionTitleDisplayed() {
+// return
+// }
+//
+// if attempt < 5 {
+// sleep(60)
+// }
+// }
+//
+// XCTFail("Failed to open 'Select an Option' screen after 5 attempts")
+ }
+
+ func tapSendButton(timeout: TimeInterval = 900) {
+ XCTAssertTrue(
+ sendButton.waitForExistence(timeout: timeout),
+ "Send button did not appear within \(timeout) seconds"
+ )
+ sendButton.tap()
+ }
+
+// func tapSendButton(timeout: TimeInterval = 900) {
+//
+// let isDisplayed = sendButton.waitForExistence(timeout: timeout)
+//
+// if !isDisplayed {
+//
+// // Attach full app hierarchy/page source into xcresult
+// let pageSourceAttachment = XCTAttachment(
+// string: app.debugDescription
+// )
+//
+// pageSourceAttachment.name = "App Debug Description XML"
+// pageSourceAttachment.lifetime = .keepAlways
+//
+// XCTContext.runActivity(
+// named: "Attach App Debug Description"
+// ) { activity in
+// activity.add(pageSourceAttachment)
+// }
+//
+// XCTFail(
+// "Send button did not appear within \(timeout) seconds"
+// )
+// }
+//
+// sendButton.tap()
+// }
+
+// func tapSwapButton() {
+// swapButton.tap()
+// }
+
+ func tapSwapButton(timeout: TimeInterval = 900) {
+ XCTAssertTrue(
+ swapButton.waitForExistence(timeout: timeout),
+ "Swap button did not appear within \(timeout) seconds"
+ )
+ swapButton.tap()
+ }
+
+}
diff --git a/ios/BitPayAppUITests/pages/SelectCurrencyPage.swift b/ios/BitPayAppUITests/pages/SelectCurrencyPage.swift
new file mode 100644
index 0000000000..9ddbdf8d80
--- /dev/null
+++ b/ios/BitPayAppUITests/pages/SelectCurrencyPage.swift
@@ -0,0 +1,68 @@
+
+//
+// OnboardingPage.swift
+// BitPayApp
+//
+// Created by vinoth vasu on 16/03/26.
+//
+
+import XCTest
+
+class SelectCurrencyPage {
+
+ let app: XCUIApplication
+
+ init(app: XCUIApplication) {
+ self.app = app
+ }
+
+ // MARK: - Elements
+
+ var selectCurrencyTitle: XCUIElement {
+ app.descendants(matching: .any).element(
+ matching: NSPredicate(format: "label == 'Select a Currency'")
+ ).firstMatch
+ }
+
+ var bitcoinCurrency: XCUIElement {
+ app.descendants(matching: .any).element(
+ matching: NSPredicate(format: "label CONTAINS 'Bitcoin, BTC'")
+ ).firstMatch
+ }
+
+ var sendToTitle: XCUIElement {
+ app.descendants(matching: .any).element(
+ matching: NSPredicate(format: "label == 'Send To'")
+ ).firstMatch
+ }
+
+ var recipientAddress: XCUIElement {
+ app.descendants(matching: .any).element(
+ matching: NSPredicate(format: "label == 'Recipient address'")
+ ).firstMatch
+ }
+
+
+ // MARK: - Actions
+
+ func isSelectCurrencyTitleDisplayed(timeout: TimeInterval = 180) -> Bool {
+ return selectCurrencyTitle.waitForExistence(timeout: timeout)
+ }
+
+ func tapBitcoinCurrency() {
+ bitcoinCurrency.tap()
+ }
+
+ func isSendToTitleDisplayed(timeout: TimeInterval = 180) -> Bool {
+ return sendToTitle.waitForExistence(timeout: timeout)
+ }
+
+ func enterRecipientAddress(address: String) {
+ recipientAddress.tap()
+ for character in address {
+ recipientAddress.typeText(String(character))
+ // usleep(100000)
+ }
+ }
+
+}
diff --git a/ios/BitPayAppUITests/pages/SelectKeyToDepositPage.swift b/ios/BitPayAppUITests/pages/SelectKeyToDepositPage.swift
new file mode 100644
index 0000000000..89ed5846f4
--- /dev/null
+++ b/ios/BitPayAppUITests/pages/SelectKeyToDepositPage.swift
@@ -0,0 +1,57 @@
+import XCTest
+
+class SelectKeyToDepositPage {
+
+ let app: XCUIApplication
+
+ init(app: XCUIApplication) {
+ self.app = app
+ }
+
+ // MARK: - Elements
+
+ var selectKeyToDeposit: XCUIElement {
+ app.staticTexts.matching(
+ NSPredicate(format: "label CONTAINS 'Select Key to Deposit to'")
+ ).firstMatch
+ }
+
+ var myKeyWallet: XCUIElement {
+ app.otherElements.matching(
+ NSPredicate(format: "label == 'My Key wallet'")
+ ).firstMatch
+ }
+
+ var secondMyKeyWallet: XCUIElement {
+ app.descendants(matching: .any)
+ .matching(NSPredicate(format: "label CONTAINS 'My Key'"))
+ .element(boundBy: 1)
+ }
+
+ var evmAccount: XCUIElement {
+ app.descendants(matching: .any).matching(
+ NSPredicate(format: "label == 'EVM Account'")
+ ).firstMatch
+ }
+
+
+ // MARK: - Validations
+
+ func isSelectKeyToDepositToDisplayed(timeout: TimeInterval = 5) -> Bool {
+ return selectKeyToDeposit.waitForExistence(timeout: timeout)
+ }
+
+ func tapMyKeyWallet() {
+ myKeyWallet.tap()
+ }
+
+ func tapSecondMyKeyWallet() {
+ secondMyKeyWallet.tap()
+ }
+
+ func tapEVMAccount() {
+ evmAccount.tap()
+ }
+
+
+}
diff --git a/ios/BitPayAppUITests/pages/SellPage.swift b/ios/BitPayAppUITests/pages/SellPage.swift
new file mode 100644
index 0000000000..1fb3b1e0d6
--- /dev/null
+++ b/ios/BitPayAppUITests/pages/SellPage.swift
@@ -0,0 +1,35 @@
+import XCTest
+
+class SellPage {
+
+ let app: XCUIApplication
+
+ init(app: XCUIApplication) {
+ self.app = app
+ }
+
+ // MARK: - Elements
+
+ var sellTitle: XCUIElement {
+ app.staticTexts["Sell"].firstMatch
+ }
+
+ var chooseCrypto: XCUIElement {
+ app.otherElements.matching(
+ NSPredicate(format: "label == 'Choose Crypto'")
+ ).firstMatch
+ }
+
+
+ // MARK: - Validations
+
+ func isSellPageTitleDisplayed(timeout: TimeInterval = 180) -> Bool {
+ return sellTitle.waitForExistence(timeout: timeout)
+ }
+
+ func tapChooseCrypto() {
+ chooseCrypto.tap()
+ }
+
+
+}
diff --git a/ios/BitPayAppUITests/pages/SwapPage.swift b/ios/BitPayAppUITests/pages/SwapPage.swift
new file mode 100644
index 0000000000..19a337d664
--- /dev/null
+++ b/ios/BitPayAppUITests/pages/SwapPage.swift
@@ -0,0 +1,189 @@
+import XCTest
+
+class SwapPage {
+
+ let app: XCUIApplication
+
+ init(app: XCUIApplication) {
+ self.app = app
+ }
+
+ // MARK: - Elements
+
+ var swapTitle: XCUIElement {
+ app.otherElements.matching(
+ NSPredicate(format: "label == 'Swap'")
+ ).firstMatch
+ }
+
+ var selectWalletFrom: XCUIElement {
+ app.otherElements.matching(
+ NSPredicate(format: "label == 'Select wallet to swap from'")
+ ).firstMatch
+ }
+
+ var cryptoToSwapPage: XCUIElement {
+ app.otherElements.matching(
+ NSPredicate(format: "label == 'Crypto to Swap'")
+ ).firstMatch
+ }
+
+ var bitcoin: XCUIElement {
+ app.otherElements.matching(
+ NSPredicate(format: "label CONTAINS 'Bitcoin, BTC'")
+ ).firstMatch
+ }
+
+ var selectWalletTo: XCUIElement {
+ app.otherElements.matching(
+ NSPredicate(format: "label == 'Select wallet to swap to'")
+ ).firstMatch
+ }
+
+ var swapTo: XCUIElement {
+ app.otherElements.matching(
+ NSPredicate(format: "label == 'Swap To'")
+ ).firstMatch
+ }
+
+ var ethereum: XCUIElement {
+ app.otherElements.matching(
+ NSPredicate(format: "label == 'Ethereum, ETH'")
+ ).firstMatch
+ }
+
+ var selectKeyToDeposit: XCUIElement {
+ app.otherElements.matching(
+ NSPredicate(format: "label == 'Select Key to Deposit to'")
+ ).firstMatch
+ }
+
+ var myKeyWallet: XCUIElement {
+ app.otherElements.matching(
+ NSPredicate(format: "label == 'My Key wallet'")
+ ).firstMatch
+ }
+
+ var evmAccount: XCUIElement {
+ app.otherElements.matching(
+ NSPredicate(format: "label == 'EVM Account'")
+ ).firstMatch
+ }
+
+ var swapCrypoButton: XCUIElement {
+ app.descendants(matching: .any).matching(
+ NSPredicate(
+ format: "label == 'Swap crypto toggle fiat display button'"
+ )
+ ).firstMatch
+ }
+
+ var enterAmount: XCUIElement {
+ app.descendants(matching: .any).matching(
+ NSPredicate(
+ format: "label == 'Swap crypto enter amount button'"
+ )
+ ).firstMatch
+ }
+
+ var minAmount: XCUIElement {
+ app.descendants(matching: .any).matching(
+ NSPredicate(
+ format: "label == 'MIN'"
+ )
+ ).firstMatch
+ }
+
+ var changellyTermsCheckbox: XCUIElement {
+ app.descendants(matching: .any).matching(
+ NSPredicate(
+ format: "identifier == 'swap-crypto-changelly-terms-checkbox'"
+ )
+ ).firstMatch
+ }
+
+ var slideToSwapButton: XCUIElement {
+ app.descendants(matching: .any).matching(
+ NSPredicate(
+ format: "label == 'Swap crypto slide to swap button'"
+ )
+ ).firstMatch
+ }
+
+
+ // MARK: - Validations
+
+ func isSwapPageTitleDisplayed(timeout: TimeInterval = 5) -> Bool {
+ return swapTitle.waitForExistence(timeout: timeout)
+ }
+
+ func tapSelectWalletFrom() {
+ selectWalletFrom.tap()
+ }
+
+ func isCryptoToSwapPageDisplayed(timeout: TimeInterval = 5) -> Bool {
+ return cryptoToSwapPage.waitForExistence(timeout: timeout)
+ }
+
+ func isBitcoinOptionDisplayed(timeout: TimeInterval = 10) -> Bool {
+ return bitcoin.waitForExistence(timeout: timeout)
+ }
+
+ func tapBitcoin() {
+ bitcoin.tap()
+ }
+
+ func tapSelectWalletTo() {
+ selectWalletTo.tap()
+ }
+
+ func isSwapToPageDisplayed(timeout: TimeInterval = 5) -> Bool {
+ return swapTo.waitForExistence(timeout: timeout)
+ }
+
+ func tapEthereum() {
+ ethereum.tap()
+ }
+
+ func isSelectKeyToDepositToDisplayed(timeout: TimeInterval = 5) -> Bool {
+ return selectKeyToDeposit.waitForExistence(timeout: timeout)
+ }
+
+ func tapMyKeyWallet() {
+ myKeyWallet.tap()
+ }
+
+ func tapSwapCrypoButton() {
+ swapCrypoButton.tap()
+ }
+
+ func tapEnterAmount() {
+ enterAmount.tap()
+ }
+
+ func isChangellyTermsCheckboxDisplayed(timeout: TimeInterval = 5) -> Bool {
+ return changellyTermsCheckbox.waitForExistence(timeout: timeout)
+ }
+
+ func tapChangellyTermsCheckbox() {
+ app.swipeUp()
+ sleep(1)
+ changellyTermsCheckbox.tap()
+ }
+
+ func tapMinAmount() {
+ minAmount.tap()
+ }
+
+ func tapCenterOfScreen() {
+ let center = app.coordinate(
+ withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)
+ )
+ center.tap()
+ }
+
+ func isSlideToSwapButtonDisplayed(timeout: TimeInterval = 10) -> Bool {
+ return slideToSwapButton.waitForExistence(timeout: timeout)
+ }
+
+}
diff --git a/ios/BitPayAppUITests/pages/VerifyYourPhrasePage.swift b/ios/BitPayAppUITests/pages/VerifyYourPhrasePage.swift
new file mode 100644
index 0000000000..dde1166147
--- /dev/null
+++ b/ios/BitPayAppUITests/pages/VerifyYourPhrasePage.swift
@@ -0,0 +1,100 @@
+import XCTest
+
+class RecoveryPhrasePage {
+
+ let app: XCUIApplication
+
+ init(app: XCUIApplication) {
+ self.app = app
+ }
+
+ // MARK: - Elements
+
+ var recoveryPhraseTitle: XCUIElement {
+ app.staticTexts["Recovery Phrase"]
+ }
+
+ var nextButton: XCUIElement {
+ app.otherElements["next-button"].firstMatch
+ }
+
+ var allStaticTexts: XCUIElementQuery {
+ app.staticTexts
+ }
+
+ var textFields: [XCUIElement] {
+ app.textFields.allElementsBoundByIndex
+ }
+
+ // MARK: - Actions
+
+ func isRecoveryPhraseDisplayed(timeout: TimeInterval = 10) -> Bool {
+ return recoveryPhraseTitle.waitForExistence(timeout: timeout)
+ }
+
+ func tapNext() {
+ XCTAssertTrue(nextButton.waitForExistence(timeout: 5))
+ nextButton.tap()
+ }
+
+ // MARK: - Business Logic
+
+ /// Extract 12 recovery words dynamically
+ func getRecoveryWords() -> [String] {
+ return allStaticTexts.allElementsBoundByIndex.compactMap { element in
+ let text = element.label.trimmingCharacters(in: .whitespaces)
+
+ // Only lowercase words (filters headers, numbers)
+ let isWord =
+ text.range(of: "^[a-z]+$", options: .regularExpression) != nil
+ return isWord ? text : nil
+ }
+ .prefix(12)
+ .map { $0 }
+ }
+
+ func extractNumbers(from text: String) -> [String] {
+ do {
+ let regex = try NSRegularExpression(pattern: "\\d+")
+ let results = regex.matches(
+ in: text,
+ range: NSRange(text.startIndex..., in: text)
+ )
+ return results.map { String(text[Range($0.range, in: text)!]) }
+ } catch {
+ return []
+ }
+ }
+
+ /// Extract indexes like 2nd, 11th, 9th dynamically
+ func getRequiredIndexes() -> [Int] {
+
+ let instructionElement = app.staticTexts.matching(
+ NSPredicate(format: "label CONTAINS 'recovery phrase correctly'")
+ ).firstMatch
+
+ XCTAssertTrue(instructionElement.waitForExistence(timeout: 5))
+
+ let instruction = instructionElement.label
+
+ return extractNumbers(from: instruction).compactMap { Int($0) }
+ }
+
+ /// Enter recovery phrase based on required indexes
+ func enterRecoveryPhrase(words: [String]) {
+
+ let indexes = getRequiredIndexes()
+
+ for (i, index) in indexes.enumerated() {
+ let word = words[index - 1]
+
+ let field = textFields[i]
+ XCTAssertTrue(field.waitForExistence(timeout: 5))
+
+ field.tap()
+ field.typeText(word)
+ }
+
+ NSLog("Entered words for indexes \(indexes)")
+ }
+}
diff --git a/ios/BitPayAppUITests/tests/Test591_OnboardingCreateWallet.swift b/ios/BitPayAppUITests/tests/Test591_OnboardingCreateWallet.swift
new file mode 100644
index 0000000000..ccb1f61567
--- /dev/null
+++ b/ios/BitPayAppUITests/tests/Test591_OnboardingCreateWallet.swift
@@ -0,0 +1,96 @@
+import XCTest
+
+final class Test591_OnboardingCreateWallet: XCTestCase {
+
+ var app: XCUIApplication!
+
+ override func setUp() {
+ continueAfterFailure = false
+
+ app = XCUIApplication()
+ app.launch()
+
+ let onboardingPage = OnboardingPage(app: app)
+ onboardingPage.handleTrackingPermissionIfDisplayed()
+ }
+
+ @MainActor
+ func test591_OnboardingCreateWallet() throws {
+
+ AllureXCTestSupport.setDisplayName(
+ "Onboarding Create Wallet (Continue Without Account)"
+ )
+ AllureXCTestSupport.addLabel(
+ "testlioManualTestID",
+ value: "003e6686-1128-4e66-b020-aa03f3b6ec67"
+ )
+ AllureXCTestSupport.addDescription(
+ "Onboarding Create Wallet without creating a new"
+ )
+
+ let onboardingPage = OnboardingPage(app: app)
+
+ AllureXCTestSupport.step(
+ "(4) On the onboarding screen “Seamlessly buy & swap”, tap Continue without an account."
+ ) {
+ onboardingPage.tapContinuewithoutAnAccount()
+ }
+
+ AllureXCTestSupport.step(
+ "(5) On the “Turn on notifications” screen, tap Skip."
+ ) {
+ onboardingPage.skipOnboarding()
+ }
+
+ AllureXCTestSupport.step(
+ "(6) On the “Protect your wallet” screen, tap Skip"
+ ) {
+ onboardingPage.skipOnboarding() //To skip protect wallet
+ }
+
+ AllureXCTestSupport.step(
+ "(7) On the “Create a key or import an existing key” screen, tap Create a Key."
+ ) {
+ onboardingPage.createWallet()
+ }
+
+ AllureXCTestSupport.step(
+ "(8) Wait until wallet creation finishes and the “Would you like to backup your key?” screen is displayed."
+ ) {
+ XCTAssertTrue(
+ onboardingPage.isBackupKeyLabelDisplayed(),
+ "Backup key page did not appear after tapping 'Create a Key'"
+ )
+ }
+
+ AllureXCTestSupport.step("(9) Tap Skip on the backup prompt screen.") {
+ onboardingPage.skipBackup() //To skip backup key
+ }
+
+ AllureXCTestSupport.step(
+ "(10) On the “Are you sure?” confirmation modal, tap Later. "
+ ) {
+ onboardingPage.tapLater()
+ }
+
+ AllureXCTestSupport.step(
+ "(11, 12 & 13) On the “Important” screen, select all the three agreements checkbox. "
+ ) {
+ onboardingPage.acceptTerms()
+ }
+
+ AllureXCTestSupport.step("(14) Tap Agree and Continue. ") {
+ onboardingPage.tapAgreeAndContinueButton()
+ }
+
+ AllureXCTestSupport.step(
+ "(15) Wait until the Home screen finishes loading. "
+ ) {
+ XCTAssertTrue(
+ onboardingPage.isYourPortfolioBalanceTextDisplayed(),
+ "Home Page - Your Portfolio Balance text is not displayed"
+ )
+ }
+ }
+
+}
diff --git a/ios/BitPayAppUITests/tests/Test592_ImportWalletRecoveryPhrase.swift b/ios/BitPayAppUITests/tests/Test592_ImportWalletRecoveryPhrase.swift
new file mode 100644
index 0000000000..85e2c34033
--- /dev/null
+++ b/ios/BitPayAppUITests/tests/Test592_ImportWalletRecoveryPhrase.swift
@@ -0,0 +1,86 @@
+import XCTest
+
+final class Test592_ImportWalletRecoveryPhrase: XCTestCase {
+
+ var app: XCUIApplication!
+
+ override func setUp() {
+ continueAfterFailure = false
+
+ app = XCUIApplication()
+ app.launch()
+
+ let onboardingPage = OnboardingPage(app: app)
+ onboardingPage.handleTrackingPermissionIfDisplayed()
+
+ AppFlows.completeOnboardingIfRequired(app: app)
+ }
+
+ @MainActor
+ func test592_ImportWalletRecoveryPhrase() throws {
+
+ AllureXCTestSupport.setDisplayName(
+ "Import Wallet via Recovery Phrase (BTC)"
+ )
+ AllureXCTestSupport.addLabel(
+ "testlioManualTestID",
+ value: "1ca663da-1909-451f-9908-25ba9cdc0396"
+ )
+ AllureXCTestSupport.addDescription(
+ "Import Wallet via Recovery Phrase (BTC)"
+ )
+
+ let portfolioBalancePage = PortfolioBalancePage(app: app)
+ let selectAnOptionPage = AddCryptoOptionPage(app: app)
+ let importRecoveryPhasePage = ImportRecoveryPhrasePage(app: app)
+ let myKeyPage = MyKeyPage(app: app)
+
+ AllureXCTestSupport.step(
+ "(1 to 3) On the Home screen, tap the Plus (+) control near the “Your Crypto” section to open wallet/key options."
+ ) {
+ portfolioBalancePage.tapAddCryptoButton()
+ }
+
+ AllureXCTestSupport.step(
+ "(4) Wait until the “Select an Option” screen is displayed."
+ ) {
+ XCTAssertTrue(
+ selectAnOptionPage.isSelectAnOptionTitleDisplayed(),
+ "Select an option page is not displayed"
+ )
+ }
+
+ AllureXCTestSupport.step("(5) Tap Import Key") {
+ selectAnOptionPage.tapImportKey()
+ }
+
+ AllureXCTestSupport.step(
+ "(6 to 7) On the “Import” screen, tap the Recovery Phrase input field & Enter a valid Recovery Phrase. "
+ ) {
+ importRecoveryPhasePage.enterRecoveryPhrase(
+ "hobby short divert lady spare quit act settle body town license alone"
+ )
+ }
+
+ AllureXCTestSupport.step("(8) Tap Import Wallet") {
+ importRecoveryPhasePage.tapImportWallet()
+ }
+
+ AllureXCTestSupport.step(
+ "(9) Wait until the import finishes and the My Key screen is displayed (use an extended wait to account for slower imports). "
+ ) {
+ XCTAssertTrue(myKeyPage.isMyKeyDisplayed(), "Is My Key not displayed")
+ XCTAssertTrue(
+ myKeyPage.isMyWalletsDisplayed(),
+ "My Wallets not displayed"
+ )
+
+ XCTAssertTrue(
+ myKeyPage.isBitcoinBTCWalletDisplayed(),
+ "Bitcoin BTC Wallet not displayed"
+ )
+ }
+
+ }
+
+}
diff --git a/ios/BitPayAppUITests/tests/Test593_BTCConfirmPaymentScreen.swift b/ios/BitPayAppUITests/tests/Test593_BTCConfirmPaymentScreen.swift
new file mode 100644
index 0000000000..0153b1db6e
--- /dev/null
+++ b/ios/BitPayAppUITests/tests/Test593_BTCConfirmPaymentScreen.swift
@@ -0,0 +1,80 @@
+import XCTest
+
+final class Test593_BTCConfirmPaymentScreen: XCTestCase {
+
+ var app: XCUIApplication!
+
+ override func setUp() {
+ continueAfterFailure = false
+
+ app = XCUIApplication()
+ app.launch()
+
+ let onboardingPage = OnboardingPage(app: app)
+ onboardingPage.handleTrackingPermissionIfDisplayed()
+
+ AppFlows.completeOnboardingIfRequired(app: app)
+ }
+
+ @MainActor
+ func test593_BTCConfirmPaymentScreen() throws {
+
+ AllureXCTestSupport.setDisplayName(
+ "Basic Send (BTC) - Confirm Payment Screen"
+ )
+ AllureXCTestSupport.addLabel(
+ "testlioManualTestID",
+ value: "be4a044a-2c3a-4cad-aa5c-9398f415b483"
+ )
+ AllureXCTestSupport.addDescription(
+ "Basic Send (BTC) - Confirm Payment Screen"
+ )
+
+ let portfolioBalancePage = PortfolioBalancePage(app: app)
+ let selectCurrencyPage = SelectCurrencyPage(app: app)
+ let enterAmountPage = EnterAmountPage(app: app)
+ let confirmPaymentPage = ConfirmPaymentPage(app: app)
+
+ AllureXCTestSupport.step("(1 to 4) On the Home screen, tap Send in the top menu.") {
+ portfolioBalancePage.tapSendButton()
+ }
+
+ AllureXCTestSupport.step("(5) Wait until the Select a Currency screen is displayed. ") {
+ XCTAssertTrue(
+ selectCurrencyPage.isSelectCurrencyTitleDisplayed(),
+ "Select Currency page not displayed"
+ )
+ }
+
+ AllureXCTestSupport.step("(6) Tap Bitcoin.") {
+ selectCurrencyPage.tapBitcoinCurrency()
+ }
+
+ AllureXCTestSupport.step("(7) Wait until the Send To screen is displayed.") {
+ selectCurrencyPage.isSendToTitleDisplayed()
+ }
+
+ AllureXCTestSupport.step("(8 & 9) Tap the Search contact or enter address field & Paste the receiver address.") {
+ selectCurrencyPage.enterRecipientAddress(address: "bc1q0effzahtsn685tyjppgukpvfhv37hrlm4g67ws")
+ }
+
+ AllureXCTestSupport.step("(10 & 11) Wait until the Amount screen is displayed & Enter 0.00005 BTC.") {
+ enterAmountPage.enterAmount(amount: "0.00005")
+ }
+
+ AllureXCTestSupport.step("(12) Tap Continue and Verify") {
+ enterAmountPage.tapContinue()
+
+ XCTAssertTrue(
+ confirmPaymentPage.isConfirmPaymentTitleDisplayed(),
+ "Confirm payment page not displayed"
+ )
+
+ XCTAssertTrue(
+ confirmPaymentPage.isSummaryTextDisplayed(),
+ "Confirm payment - Summary text not displayed"
+ )
+
+ }
+ }
+}
diff --git a/ios/BitPayAppUITests/tests/Test594_BasicSwapBTC.swift b/ios/BitPayAppUITests/tests/Test594_BasicSwapBTC.swift
new file mode 100644
index 0000000000..0c5c922712
--- /dev/null
+++ b/ios/BitPayAppUITests/tests/Test594_BasicSwapBTC.swift
@@ -0,0 +1,94 @@
+import XCTest
+
+final class Test594_BasicSwapBTC: XCTestCase {
+
+ var app: XCUIApplication!
+
+ override func setUp() {
+ continueAfterFailure = false
+
+ app = XCUIApplication()
+ app.launch()
+
+ let onboardingPage = OnboardingPage(app: app)
+ onboardingPage.handleTrackingPermissionIfDisplayed()
+
+ AppFlows.completeOnboardingIfRequired(app: app)
+ }
+
+ @MainActor
+ func test594_BasicSwapBTC() throws {
+
+ AllureXCTestSupport.setDisplayName(
+ "Basic Swap BTC to ETH"
+ )
+ AllureXCTestSupport.addLabel(
+ "testlioManualTestID",
+ value: "947f685f-e106-4708-922f-43419f9f8f50"
+ )
+ AllureXCTestSupport.addDescription(
+ "Basic Swap BTC to ETH (View Offers)"
+ )
+
+ let portfolioBalancePage = PortfolioBalancePage(app: app)
+ let swapPage = SwapPage(app: app)
+ let selectKeyToDepositPage = SelectKeyToDepositPage(app: app)
+ let enterAmountPage = EnterAmountPage(app: app)
+ let selectCurrencyPage = SelectCurrencyPage(app: app)
+
+ AllureXCTestSupport.step("(1 to 4) From the Home screen, tap Swap in the top menu. ") {
+ portfolioBalancePage.tapSwapButton()
+ }
+
+ AllureXCTestSupport.step("(5) Wait until the Swap Crypto screen is displayed.") {
+ XCTAssertTrue(swapPage.isSwapPageTitleDisplayed(), "Swap page not displayed")
+ }
+
+ AllureXCTestSupport.step("(6) In the Swap From section, tap Select Wallet") {
+ swapPage.tapSelectWalletFrom()
+ }
+
+ AllureXCTestSupport.step("(7) On the Crypto to Swap screen, tap Bitcoin (BTC)") {
+ XCTAssertTrue(swapPage.isBitcoinOptionDisplayed(), "Bitcoin option not displayed")
+ swapPage.tapBitcoin()
+ }
+
+ AllureXCTestSupport.step("(8) Back on the Swap Crypto screen, in the Swap To section, tap Select Crypto.") {
+ swapPage.tapSelectWalletTo()
+ }
+
+ AllureXCTestSupport.step("(9) On the Swap To list screen, tap Ethereum (ETH).") {
+ swapPage.tapEthereum()
+ }
+
+ AllureXCTestSupport.step("(10) On the Select Key to Deposit to screen, tap My Key (choose the entry that has funds available if multiple are shown).") {
+ XCTAssertTrue(selectKeyToDepositPage.isSelectKeyToDepositToDisplayed(), "Select Key to Deposit to dialog not displayed")
+ selectKeyToDepositPage.tapSecondMyKeyWallet()
+ }
+
+ AllureXCTestSupport.step("(11) On the Swap Crypto screen, tap Enter Amount.") {
+ selectKeyToDepositPage.tapEVMAccount()
+ swapPage.tapSwapCrypoButton()
+ }
+
+ AllureXCTestSupport.step("(12) On the Swap Amount bottom sheet, enter 0.0007 BTC.") {
+ swapPage.tapEnterAmount()
+ enterAmountPage.enterAmount(amount: "0.0007")
+ }
+
+ AllureXCTestSupport.step("(13) Tap Changelly checkbox.") {
+ swapPage.tapCenterOfScreen()
+ XCTAssertTrue(swapPage.isChangellyTermsCheckboxDisplayed(), "Changelly Terms and Conditions checkbox not displayed")
+ swapPage.tapChangellyTermsCheckbox()
+ }
+
+ AllureXCTestSupport.step(
+ "(14) Verify Slide to Swap button is displayed."
+ ) {
+ XCTAssertTrue(
+ swapPage.isSlideToSwapButtonDisplayed(),
+ "Slide to Swap button not displayed"
+ )
+ }
+ }
+}
diff --git a/ios/BitPayAppUITests/tests/Test595_BasicSellBTC_.swift b/ios/BitPayAppUITests/tests/Test595_BasicSellBTC_.swift
new file mode 100644
index 0000000000..f22bc3716a
--- /dev/null
+++ b/ios/BitPayAppUITests/tests/Test595_BasicSellBTC_.swift
@@ -0,0 +1,64 @@
+import XCTest
+
+final class Test595_BasicSellBTC: XCTestCase {
+
+ var app: XCUIApplication!
+
+ override func setUp() {
+ continueAfterFailure = false
+
+ app = XCUIApplication()
+ app.launch()
+
+ let onboardingPage = OnboardingPage(app: app)
+ onboardingPage.handleTrackingPermissionIfDisplayed()
+
+ AppFlows.completeOnboardingIfRequired(app: app)
+ }
+
+ @MainActor
+ func test595_BasicSellBTC() throws {
+
+ AllureXCTestSupport.setDisplayName(
+ "Basic Sell BTC"
+ )
+ AllureXCTestSupport.addLabel(
+ "testlioManualTestID",
+ value: "dfc4301a-fad9-4d7f-a13a-91ca1f1d54a7"
+ )
+ AllureXCTestSupport.addDescription(
+ "Basic Sell BTC (Continue With Opens Browser)"
+ )
+
+ let portfolioBalancePage = PortfolioBalancePage(app: app)
+ let sellPage = SellPage(app: app)
+ let enterAmountPage = EnterAmountPage(app: app)
+ let selectCurrencyPage = SelectCurrencyPage(app: app)
+
+ AllureXCTestSupport.step("(1 to 4) From the Home screen, tap Sell.") {
+ portfolioBalancePage.tapSellButton()
+ }
+
+ AllureXCTestSupport.step("(5) Wait until the Sell screen is displayed.") {
+ XCTAssertTrue(sellPage.isSellPageTitleDisplayed(), "Sell page not displayed")
+ }
+
+ AllureXCTestSupport.step("(6) Enter $30 as the sell amount.") {
+ enterAmountPage.enterAmount(amount: "0.007")
+ }
+
+ AllureXCTestSupport.step("(7) Tap Choose Crypto.") {
+ sellPage.tapChooseCrypto()
+ }
+
+ AllureXCTestSupport.step("(8) On the Select Crypto screen, tap Bitcoin (BTC). ") {
+ selectCurrencyPage.tapBitcoinCurrency()
+ //tap swap currency
+ }
+
+ AllureXCTestSupport.step("(9) Tap Continue with.") {
+ enterAmountPage.tapContinue()
+ }
+
+ }
+}
diff --git a/ios/BitPayAppUITests/tests/Test596_BasicBuyBTC_.swift b/ios/BitPayAppUITests/tests/Test596_BasicBuyBTC_.swift
new file mode 100644
index 0000000000..895d6aeebe
--- /dev/null
+++ b/ios/BitPayAppUITests/tests/Test596_BasicBuyBTC_.swift
@@ -0,0 +1,74 @@
+import XCTest
+
+final class Test596_BasicBuyBTC: XCTestCase {
+
+ var app: XCUIApplication!
+
+ override func setUp() {
+ continueAfterFailure = false
+
+ app = XCUIApplication()
+ app.launch()
+
+ let onboardingPage = OnboardingPage(app: app)
+ onboardingPage.handleTrackingPermissionIfDisplayed()
+
+ AppFlows.completeOnboardingIfRequired(app: app)
+ }
+
+ @MainActor
+ func test596_BasicBuyBTC() throws {
+
+ AllureXCTestSupport.setDisplayName(
+ "Basic Buy BTC"
+ )
+ AllureXCTestSupport.addLabel(
+ "testlioManualTestID",
+ value: "3c9c0271-76ba-4da3-88ad-8a5d6ea60211"
+ )
+ AllureXCTestSupport.addDescription(
+ "Basic Buy BTC (Best Offer → Continue With Opens Browser)"
+ )
+
+ let portfolioBalancePage = PortfolioBalancePage(app: app)
+ let buyPage = BuyPage(app: app)
+ let selectKeyToDepositPage = SelectKeyToDepositPage(app: app)
+ let enterAmountPage = EnterAmountPage(app: app)
+ let selectCurrencyPage = SelectCurrencyPage(app: app)
+
+ AllureXCTestSupport.step("(1 to 4) From the Home screen, tap Buy.") {
+ portfolioBalancePage.tapBuyButton()
+ }
+
+ AllureXCTestSupport.step("(5) Wait until the Buy screen is displayed. ") {
+ XCTAssertTrue(buyPage.isBuyPageTitleDisplayed(), "Buy page not displayed")
+ }
+
+ AllureXCTestSupport.step("(6) Enter $30 as the buy amount.") {
+ enterAmountPage.tapBackspace(count: 3)
+ enterAmountPage.enterAmount(amount: "30")
+ }
+
+ AllureXCTestSupport.step("(7) Tap Choose Crypto.") {
+ buyPage.tapBitcoin()
+ }
+
+ AllureXCTestSupport.step("(8) On the Select Crypto screen, tap Bitcoin (BTC). ") {
+ selectCurrencyPage.tapBitcoinCurrency()
+ }
+
+ AllureXCTestSupport.step("(9) On the Select Key to Deposit to screen, tap My Key with funds available.") {
+ XCTAssertTrue(selectKeyToDepositPage.isSelectKeyToDepositToDisplayed(), "Select Key to Deposit to dialog not displayed")
+ selectKeyToDepositPage.tapMyKeyWallet()
+ }
+
+ AllureXCTestSupport.step("(10) Wait until best-offer search completes and at least one offer is displayed.") {
+ }
+
+ AllureXCTestSupport.step("(11) Tap Continue with {ProviderName}.") {
+ }
+
+ AllureXCTestSupport.step("(12) Wait for the browser / in-app webview to open.") {
+ }
+ }
+}
diff --git a/ios/BitPayAppUITests/tests/TestPlan.xctestplan b/ios/BitPayAppUITests/tests/TestPlan.xctestplan
new file mode 100644
index 0000000000..e40ed87ae9
--- /dev/null
+++ b/ios/BitPayAppUITests/tests/TestPlan.xctestplan
@@ -0,0 +1,27 @@
+{
+ "configurations" : [
+ {
+ "id" : "0232DDA3-81F2-4B5D-9BC1-2EB32AECE399",
+ "name" : "Configuration 1",
+ "options" : {
+
+ }
+ }
+ ],
+ "defaultOptions" : {
+ "testTimeoutsEnabled" : true,
+ "uiTestingScreenshotsLifetime" : "keepAlways",
+ "userAttachmentLifetime" : "keepAlways",
+ "systemAttachmentLifetime" : "keepAlways"
+ },
+ "testTargets" : [
+ {
+ "target" : {
+ "containerPath" : "container:BitPayApp.xcodeproj",
+ "identifier" : "CDF085062FACD8A500CA4C1B",
+ "name" : "BitPayAppUITests"
+ }
+ }
+ ],
+ "version" : 1
+}
diff --git a/scripts/run-tests.sh b/scripts/run-tests.sh
new file mode 100644
index 0000000000..065c21e704
--- /dev/null
+++ b/scripts/run-tests.sh
@@ -0,0 +1,34 @@
+#!/bin/bash
+
+OVERALL_EXIT=0
+
+for TEST_CLASS in \
+ "Test591OnboardingCreateWallet" \
+ "Test592ImportWalletRecoveryPhrase" \
+ "Test593SendBTC" \
+ "Test594SwapBTC" \
+ "Test595SellBTC" \
+ "Test596BuyBTC"
+do
+ echo "=== Clearing state before $TEST_CLASS ==="
+ adb shell pm clear com.bitpay.wallet || true
+ sleep 2
+
+ echo "=== Running $TEST_CLASS ==="
+ cd android && ./gradlew connectedDebugAndroidTest \
+ -PreactNativeArchitectures=x86_64 \
+ -Pandroid.testInstrumentationRunnerArguments.class="com.bitpay.wallet.tests.$TEST_CLASS" \
+ --no-daemon
+ EXIT=$?
+ cd ..
+
+ [ $EXIT -ne 0 ] && OVERALL_EXIT=$EXIT
+
+ # Pull allure results immediately after each test
+ # so a timeout or crash on a later test doesn't lose earlier results
+ echo "=== Pulling allure results after $TEST_CLASS ==="
+ mkdir -p /home/runner/work/_temp/allure-results
+ adb pull /sdcard/googletest/test_outputfiles/allure-results/. /home/runner/work/_temp/allure-results/ || true
+done
+
+exit $OVERALL_EXIT
\ No newline at end of file
diff --git a/testlio-cli/project-config.json b/testlio-cli/project-config.json
new file mode 100644
index 0000000000..1ea5da9269
--- /dev/null
+++ b/testlio-cli/project-config.json
@@ -0,0 +1,10 @@
+{
+ "baseURI": "https://api.testlio.com/",
+ "platformURI": "https://app.testlio.com/tmt/project/",
+ "projectId": "3307",
+ "automatedRunCollectionGuid": "2df42fa4-225b-430e-882d-e5fc3ec31de7",
+ "testRunCollectionGuid": "a77e695c-5061-4451-98fe-4f93b8a26d9b",
+ "resultCollectionGuid": "3688212d-e132-40a3-a4bd-aae8b664a2d8",
+ "workspaceName": "bitpay---sandbox"
+}
+
diff --git a/testlio-cli/test-config.json b/testlio-cli/test-config.json
new file mode 100644
index 0000000000..2986f99020
--- /dev/null
+++ b/testlio-cli/test-config.json
@@ -0,0 +1,4 @@
+{
+ "automatedTestNamePrefix": "Automation Run: Smoke Test"
+}
+