Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ If you have `bitkit-e2e-tests`, `bitkit-android`, and `bitkit-ios` checked out i
# Legacy RN Android (builds ../bitkit and copies APK to ./aut/bitkit_rn_regtest.apk)
./scripts/build-rn-android-apk.sh

# Legacy RN iOS simulator (builds ../bitkit and copies app to ./aut/bitkit_rn_regtest_ios.app)
./scripts/build-rn-ios-sim.sh

# iOS (builds ../bitkit-ios and copies IPA to ./aut/bitkit_e2e.ipa)
./scripts/build-ios-sim.sh
```
Expand All @@ -73,6 +76,9 @@ BACKEND=regtest ./scripts/build-android-apk.sh
# Legacy RN Android
BACKEND=regtest ./scripts/build-rn-android-apk.sh

# Legacy RN iOS simulator
BACKEND=regtest ./scripts/build-rn-ios-sim.sh

# iOS
BACKEND=local ./scripts/build-ios-sim.sh
BACKEND=regtest ./scripts/build-ios-sim.sh
Expand Down
2 changes: 1 addition & 1 deletion scripts/build-rn-android-apk.sh
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ fi

if [[ -z "${ENV_FILE:-}" ]]; then
if [[ "$BACKEND" == "regtest" ]]; then
ENV_FILE=".env.test.template"
ENV_FILE=".env.development.template"
else
ENV_FILE=".env.development"
fi
Expand Down
73 changes: 73 additions & 0 deletions scripts/build-rn-ios-sim.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#!/usr/bin/env bash
# Build the legacy Bitkit RN iOS simulator app from ../bitkit and copy into aut/
#
# Inputs/roots:
# - E2E root: this repo (bitkit-e2e-tests)
# - RN root: ../bitkit (resolved relative to this script)
#
# Output:
# - Copies .app -> aut/bitkit_rn_regtest_ios.app
#
# Usage:
# ./scripts/build-rn-ios-sim.sh [debug|release]
# BACKEND=regtest ./scripts/build-rn-ios-sim.sh
# ENV_FILE=.env.test.template ./scripts/build-rn-ios-sim.sh
set -euo pipefail

E2E_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
RN_ROOT="$(cd "$E2E_ROOT/../bitkit" && pwd)"

BUILD_TYPE="${1:-debug}"
BACKEND="${BACKEND:-regtest}"

if [[ "$BUILD_TYPE" != "debug" && "$BUILD_TYPE" != "release" ]]; then
echo "ERROR: Unsupported build type: $BUILD_TYPE (expected debug|release)" >&2
exit 1
fi

if [[ -z "${ENV_FILE:-}" ]]; then
if [[ "$BACKEND" == "regtest" ]]; then
ENV_FILE=".env.development.template"
else
ENV_FILE=".env.development"
fi
fi

if [[ ! -f "$RN_ROOT/$ENV_FILE" ]]; then
echo "ERROR: Env file not found: $RN_ROOT/$ENV_FILE" >&2
exit 1
fi

echo "Building RN iOS simulator app (BACKEND=$BACKEND, ENV_FILE=$ENV_FILE, BUILD_TYPE=$BUILD_TYPE)..."

pushd "$RN_ROOT" >/dev/null
if [[ -f .env ]]; then
cp .env .env.bak
fi
cp "$ENV_FILE" .env
E2E_TESTS=true yarn "e2e:build:ios-$BUILD_TYPE"
if [[ -f .env.bak ]]; then
mv .env.bak .env
else
rm -f .env
fi
popd >/dev/null

if [[ "$BUILD_TYPE" == "debug" ]]; then
IOS_CONFIG="Debug"
else
IOS_CONFIG="Release"
fi

APP_PATH="$RN_ROOT/ios/build/Build/Products/${IOS_CONFIG}-iphonesimulator/bitkit.app"
if [[ ! -d "$APP_PATH" ]]; then
echo "ERROR: iOS .app not found at: $APP_PATH" >&2
exit 1
fi

OUT="$E2E_ROOT/aut"
mkdir -p "$OUT"
OUT_APP="$OUT/bitkit_rn_${BACKEND}_ios.app"
rm -rf "$OUT_APP"
cp -R "$APP_PATH" "$OUT_APP"
echo "RN iOS simulator app copied to: $OUT_APP"
72 changes: 47 additions & 25 deletions test/helpers/actions.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { ChainablePromiseElement } from 'webdriverio';
import { reinstallApp } from './setup';
import BitcoinJsonRpc from 'bitcoin-json-rpc';
import { deposit, mineBlocks } from './regtest';

export const sleep = (ms: number) => browser.pause(ms);

Expand Down Expand Up @@ -88,11 +88,11 @@ export function elementByText(
} else {
if (strategy === 'exact') {
return $(
`-ios predicate string:(type == "XCUIElementTypeStaticText" OR type == "XCUIElementTypeButton") AND (label == "${text}" OR value == "${text}")`
`-ios predicate string:(type == "XCUIElementTypeStaticText" OR type == "XCUIElementTypeButton" OR type == "XCUIElementTypeOther") AND (label == "${text}" OR value == "${text}")`
);
}
return $(
`-ios predicate string:(type == "XCUIElementTypeStaticText" OR type == "XCUIElementTypeButton") AND label CONTAINS "${text}"`
`-ios predicate string:(type == "XCUIElementTypeStaticText" OR type == "XCUIElementTypeButton" OR type == "XCUIElementTypeOther") AND label CONTAINS "${text}"`
);
}
}
Expand Down Expand Up @@ -522,14 +522,19 @@ export async function restoreWallet(
{
passphrase,
expectQuickPayTimedSheet = false,
}: { passphrase?: string; expectQuickPayTimedSheet?: boolean } = {}
expectBackupSheet = false,
reinstall = true,
}: { passphrase?: string; expectQuickPayTimedSheet?: boolean; expectBackupSheet?: boolean; reinstall?: boolean } = {}
) {
console.info('→ Restoring wallet with seed:', seed);
// Let cloud state flush - carried over from Detox
await sleep(5000);

// Reinstall app to wipe all data
await reinstallApp();
if (reinstall) {
console.info('Reinstalling app to reset state...');
await reinstallApp();
}

// Terms of service
await elementById('Continue').waitForDisplayed();
Expand Down Expand Up @@ -563,11 +568,15 @@ export async function restoreWallet(

// Wait for Get Started
const getStarted = await elementById('GetStartedButton');
await getStarted.waitForDisplayed();
await getStarted.waitForDisplayed({ timeout: 120000 });
await tap('GetStartedButton');
await sleep(1000);
await handleAndroidAlert();

if (expectBackupSheet) {
await dismissBackupTimedSheet();
}

if (expectQuickPayTimedSheet) {
await dismissQuickPayIntro();
}
Expand Down Expand Up @@ -642,37 +651,50 @@ export async function getAddressFromQRCode(which: addressType): Promise<string>
return address;
}

export async function mineBlocks(rpc: BitcoinJsonRpc, blocks: number = 1) {
for (let i = 0; i < blocks; i++) {
await rpc.generateToAddress(1, await rpc.getNewAddress());
/**
* Funds the wallet on regtest.
* Gets the receive address from the app, deposits sats, and optionally mines blocks.
* Uses local Bitcoin RPC or Blocktank API based on BACKEND env var.
*/
export async function fundOnchainWallet({
sats,
blocksToMine = 1,
}: {
sats?: number;
blocksToMine?: number;
} = {}) {
const address = await getReceiveAddress();
await swipeFullScreen('down');
await deposit(address, sats);
if (blocksToMine > 0) {
await mineBlocks(blocksToMine);
}
}

export async function receiveOnchainFunds(
rpc: BitcoinJsonRpc,
{
sats = 100_000,
blocksToMine = 1,
expectHighBalanceWarning = false,
}: {
sats?: number;
blocksToMine?: number;
expectHighBalanceWarning?: boolean;
} = {}
) {
// convert sats → btc string
const btc = (sats / 100_000_000).toString();
/**
* Receives onchain funds and verifies the balance.
* Uses local Bitcoin RPC or Blocktank API based on BACKEND env var.
*/
export async function receiveOnchainFunds({
sats = 100_000,
blocksToMine = 1,
expectHighBalanceWarning = false,
}: {
sats?: number;
blocksToMine?: number;
expectHighBalanceWarning?: boolean;
} = {}) {
// format sats with spaces every 3 digits
const formattedSats = sats.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ' ');

// receive some first
const address = await getReceiveAddress();
await swipeFullScreen('down');
await rpc.sendToAddress(address, btc);
await deposit(address, sats);

await acknowledgeReceivedPayment();

await mineBlocks(rpc, blocksToMine);
await mineBlocks(blocksToMine);

const moneyText = await elementByIdWithin('TotalBalance-primary', 'MoneyText');
await expect(moneyText).toHaveText(formattedSats);
Expand Down
7 changes: 6 additions & 1 deletion test/helpers/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,15 @@ export function getAppPath(): string {
throw new Error(`App path not defined in capabilities (tried ${possibleKeys.join(', ')})`);
}

export const bitcoinURL = 'http://polaruser:polarpass@127.0.0.1:43782';
export const bitcoinURL =
process.env.BITCOIN_RPC_URL ?? 'http://polaruser:polarpass@127.0.0.1:43782';
export const electrumHost = '127.0.0.1';
export const electrumPort = 60001;

// Blocktank API for regtest operations (deposit, mine blocks, pay invoices)
export const blocktankURL =
process.env.BLOCKTANK_URL ?? 'https://api.stag0.blocktank.to/blocktank/api/v2';

export type LndConfig = {
server: string;
tls: string;
Expand Down
29 changes: 26 additions & 3 deletions test/helpers/electrum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import tls from 'tls';
import BitcoinJsonRpc from 'bitcoin-json-rpc';
import * as electrum from 'rn-electrum-client/helpers';
import { bitcoinURL, electrumHost, electrumPort } from './constants';
import { getBackend } from './regtest';

const peer = {
host: electrumHost,
Expand All @@ -17,11 +18,33 @@ function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}

// Connect to the Bitcoin Core node and Electrum server to wait for Electrum to sync
const initElectrum = async (): Promise<{
export type ElectrumClient = {
waitForSync: () => Promise<void>;
stop: () => Promise<void>;
}> => {
};

// No-op electrum client for regtest backend (app connects to remote Electrum directly)
const noopElectrum: ElectrumClient = {
waitForSync: async () => {
// For regtest backend, we just wait a bit for the app to sync with remote Electrum
console.info('→ [regtest] Waiting for app to sync with remote Electrum...');
await sleep(2000);
},
stop: async () => {
// Nothing to stop for regtest
},
};

// Connect to the Bitcoin Core node and Electrum server to wait for Electrum to sync
const initElectrum = async (): Promise<ElectrumClient> => {
const backend = getBackend();

// For regtest backend, return no-op client (app connects to remote Electrum directly)
if (backend !== 'local') {
console.info(`→ [${backend}] Skipping local Electrum init (using remote Electrum)`);
return noopElectrum;
}

let electrumHeight = 0;

try {
Expand Down
Loading