tst: e2e stability

This commit is contained in:
Ivan Vershigora 2026-05-11 12:26:15 +01:00 committed by Ivan
parent 8195855f05
commit 295a32caef
6 changed files with 52 additions and 26 deletions

View File

@ -5,11 +5,12 @@ export interface BadgeProps {
value?: string | number | React.ReactNode;
badgeStyle?: StyleProp<ViewStyle>;
textStyle?: StyleProp<TextStyle>;
testID?: string;
}
const Badge: React.FC<BadgeProps> = ({ value, badgeStyle, textStyle }) => {
const Badge: React.FC<BadgeProps> = ({ value, badgeStyle, textStyle, testID }) => {
return (
<View style={[styles.badge, badgeStyle]}>
<View testID={testID} style={[styles.badge, badgeStyle]}>
{typeof value === 'string' || typeof value === 'number' ? <Text style={[styles.text, textStyle]}>{value}</Text> : value}
</View>
);

View File

@ -27,6 +27,7 @@ const FrozenBadge: React.FC = () => {
const { colors } = useTheme();
return (
<Badge
testID="FrozenBadge"
value={loc.cc.freeze}
badgeStyle={[styles.badge, { backgroundColor: colors.redBG }]}
textStyle={[styles.badgeText, { color: colors.redText }]}
@ -38,6 +39,7 @@ const ChangeBadge: React.FC = () => {
const { colors } = useTheme();
return (
<Badge
testID="ChangeBadge"
value={loc.cc.change}
badgeStyle={[styles.badge, { backgroundColor: colors.buttonDisabledBackgroundColor }]}
textStyle={[styles.badgeText, { color: colors.alternativeTextColor }]}
@ -135,7 +137,7 @@ const OutputList: React.FC<TOutputListProps> = ({
/>
<View style={styles.itemContent}>
<Text style={oStyles.amount}>{amount}</Text>
<Text style={oStyles.memo} numberOfLines={1} ellipsizeMode="middle">
<Text testID="OutputMemoLabel" style={oStyles.memo} numberOfLines={1} ellipsizeMode="middle">
{memo || address}
</Text>
</View>

View File

@ -44,6 +44,7 @@ const CoinControlOutputSheet: React.FC = () => {
const switchValue = useMemo(
() => ({
value: frozen,
testID: 'FreezeSwitch',
onValueChange: async (value: boolean) => {
if (!wallet) return;
setFrozen(value);

View File

@ -756,7 +756,7 @@ describe('BlueWallet UI Tests - no wallets', () => {
// wait for discovery to be completed
await waitFor(element(by.text("m/84'/0'/0'")))
.toBeVisible()
.withTimeout(300 * 1000);
.withTimeout(600 * 1000);
await expect(element(by.text("m/44'/0'/1'"))).toBeVisible();
await expect(element(by.text("m/49'/0'/0'"))).toBeVisible();
await expect(element(by.id('Loading'))).not.toBeVisible();
@ -770,7 +770,7 @@ describe('BlueWallet UI Tests - no wallets', () => {
await waitForKeyboardToClose();
await waitFor(element(by.text('Found'))) // wait for discovery to be completed
.toExist()
.withTimeout(300 * 1000);
.withTimeout(600 * 1000);
await element(by.text('Found')).tap();
await element(by.id('ImportButton')).tap();
await element(by.text('OK')).tap();

View File

@ -19,6 +19,7 @@ import {
typeTextIntoAlertInput,
waitForId,
waitForKeyboardToClose,
waitForSwitchValue,
waitForText,
} from './helperz';
@ -698,15 +699,12 @@ describe('BlueWallet UI Tests - import BIP84 wallet', () => {
await element(by.id('OutputMemo')).typeText('Test2');
await element(by.id('OutputMemo')).tapReturnKey();
await waitForKeyboardToClose();
if (device.getPlatform() === 'ios') {
// FIXME. Add testId to freez switch
await element(by.type('UISwitchModernVisualElement')).tap(); // freeze switch
} else {
await element(by.type('android.widget.CompoundButton')).tap(); // freeze switch
}
await element(by.id('FreezeSwitch')).tap(); // freeze switch
await waitForSwitchValue('FreezeSwitch', true);
await element(by.id('CoinControlOutputDone')).tap();
await expect(element(by.text('Test2')).atIndex(0)).toBeVisible();
await expect(element(by.text('Freeze')).atIndex(0)).toBeVisible();
await waitFor(element(by.id('CoinControlOutputDone'))).not.toBeVisible().withTimeout(20000);
await expect(element(by.id('OutputMemoLabel').and(by.text('Test2')))).toBeVisible();
await expect(element(by.id('FrozenBadge'))).toBeVisible();
// use frozen output to create tx using "Use coin" feature
await element(by.text('Test2')).atIndex(0).tap();

View File

@ -93,6 +93,24 @@ export async function getSwitchValue(switchId) {
}
}
// Detox's waitFor() doesn't expose toHaveToggleValue, so poll expect() until the switch reaches
// the expected state (or throw after timeoutMs).
export async function waitForSwitchValue(switchId, expectedValue, timeoutMs = 8000) {
const callsite = captureCallsite(waitForSwitchValue);
const deadline = Date.now() + timeoutMs;
let lastErr;
while (Date.now() < deadline) {
try {
await expect(element(by.id(switchId))).toHaveToggleValue(expectedValue);
return;
} catch (err) {
lastErr = err;
await sleep(250);
}
}
rethrowWithCallsite(lastErr || new Error(`Timed out waiting for ${switchId} == ${expectedValue}`), callsite);
}
export async function helperImportWallet(importText, walletType, expectedWalletLabel, expectedBalance, passphrase) {
await waitForId('WalletsList');
await waitFor(element(by.id('CreateAWallet')))
@ -333,23 +351,29 @@ export async function setCustomFeeRate(feeRate) {
}
export async function goBack() {
if (device.getPlatform() === 'ios') {
try {
await element(by.id('BackButton')).atIndex(0).tap();
} catch (_backError) {
if (device.getPlatform() !== 'ios') {
await device.pressBack();
return;
}
const callsite = captureCallsite(goBack);
// Try each back/close affordance in order; retry the full set up to 10 times.
const candidates = [by.id('BackButton'), by.id('NavigationCloseButton'), by.label('Back'), by.text('Close')];
for (let attempt = 0; attempt < 10; attempt++) {
for (const matcher of candidates) {
try {
await element(by.id('NavigationCloseButton')).atIndex(0).tap();
} catch (_closeButtonError) {
try {
await element(by.label('Back')).atIndex(0).tap();
} catch (_backLabelError) {
await element(by.text('Close')).atIndex(0).tap();
}
await element(matcher).atIndex(0).tap();
return;
} catch (_) {
/* try next */
}
}
} else {
await device.pressBack();
await sleep(500);
}
rethrowWithCallsite(new Error('goBack: no back/close affordance tappable after 10 attempts.'), callsite);
}
export async function typeTextIntoAlertInput(text) {