Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
4 changes: 4 additions & 0 deletions app/components/UI/Predict/Predict.testIds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,10 @@ export const PredictBalanceSelectorsIDs = {
BALANCE_CARD: 'predict-balance-card',
} as const;

export const PredictBalanceSelectorsText = {
AVAILABLE_BALANCE: enContent.predict.available_balance,
} as const;

// ========================================
// PREDICT ADD FUNDS SELECTORS
// ========================================
Expand Down
6 changes: 6 additions & 0 deletions tests/framework/GestureStrategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ export interface UnifiedGestureOptions {
speed?: 'fast' | 'slow';
/** Swipe percentage (0–1) — Detox only; Appium ignores this */
percentage?: number;
/** Scroll direction — Detox only; used by scrollToElement */
direction?: 'up' | 'down' | 'left' | 'right';
/** Scroll amount in px — Detox only; used by scrollToElement */
scrollAmount?: number;
}

/**
Expand Down Expand Up @@ -220,6 +224,8 @@ export class DetoxGestureStrategy implements GestureStrategy {
{
timeout: opts?.timeout,
elemDescription: opts?.description,
direction: opts?.direction,
scrollAmount: opts?.scrollAmount,
},
);
}
Expand Down
285 changes: 239 additions & 46 deletions tests/page-objects/Confirmation/TransactionPayConfirmation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,80 +2,260 @@ import {
ConfirmationRowComponentIDs,
TransactionPayComponentIDs,
} from '../../../app/components/Views/confirmations/ConfirmationView.testIds';
import Matchers from '../../framework/Matchers';
import { Assertions, Gestures } from '../../framework';
import {
Assertions,
FrameworkDetector,
Matchers,
PlaywrightMatchers,
UnifiedGestures,
asDetoxElement,
asPlaywrightElement,
encapsulated,
encapsulatedAction,
type EncapsulatedElementType,
} from '../../framework';

class TransactionPayConfirmation {
get bridgeTime(): DetoxElement {
return Matchers.getElementByID(ConfirmationRowComponentIDs.BRIDGE_TIME);
get bridgeTime(): EncapsulatedElementType {
return encapsulated({
detox: () =>
Matchers.getElementByID(ConfirmationRowComponentIDs.BRIDGE_TIME),
appium: () =>
PlaywrightMatchers.getElementById(
ConfirmationRowComponentIDs.BRIDGE_TIME,
{
exact: true,
},
),
});
}

get payWithRow(): DetoxElement {
return Matchers.getElementByID(ConfirmationRowComponentIDs.PAY_WITH);
get payWithRow(): EncapsulatedElementType {
return encapsulated({
detox: () =>
Matchers.getElementByID(ConfirmationRowComponentIDs.PAY_WITH),
appium: () =>
PlaywrightMatchers.getElementById(
ConfirmationRowComponentIDs.PAY_WITH,
{
exact: true,
},
),
});
}

get payWithSymbol(): DetoxElement {
return Matchers.getElementByID(TransactionPayComponentIDs.PAY_WITH_SYMBOL);
get payWithSymbol(): EncapsulatedElementType {
return encapsulated({
detox: () =>
Matchers.getElementByID(TransactionPayComponentIDs.PAY_WITH_SYMBOL),
appium: () =>
PlaywrightMatchers.getElementById(
TransactionPayComponentIDs.PAY_WITH_SYMBOL,
{
exact: true,
},
),
});
}

get payWithFiat(): DetoxElement {
return Matchers.getElementByID(TransactionPayComponentIDs.PAY_WITH_FIAT);
get payWithFiat(): EncapsulatedElementType {
return encapsulated({
detox: () =>
Matchers.getElementByID(TransactionPayComponentIDs.PAY_WITH_FIAT),
appium: () =>
PlaywrightMatchers.getElementById(
TransactionPayComponentIDs.PAY_WITH_FIAT,
{
exact: true,
},
),
});
}

get payWithBalance(): DetoxElement {
return Matchers.getElementByID(TransactionPayComponentIDs.PAY_WITH_BALANCE);
get payWithBalance(): EncapsulatedElementType {
return encapsulated({
detox: () =>
Matchers.getElementByID(TransactionPayComponentIDs.PAY_WITH_BALANCE),
appium: () =>
PlaywrightMatchers.getElementById(
TransactionPayComponentIDs.PAY_WITH_BALANCE,
{
exact: true,
},
),
});
}

get keyboardContinueButton(): DetoxElement {
return Matchers.getElementByID(
TransactionPayComponentIDs.KEYBOARD_CONTINUE_BUTTON,
);
get keyboardContinueButton(): EncapsulatedElementType {
return encapsulated({
detox: () =>
Matchers.getElementByID(
TransactionPayComponentIDs.KEYBOARD_CONTINUE_BUTTON,
),
appium: () =>
PlaywrightMatchers.getElementById(
TransactionPayComponentIDs.KEYBOARD_CONTINUE_BUTTON,
{
exact: true,
},
),
});
}

get total(): DetoxElement {
return Matchers.getElementByID(ConfirmationRowComponentIDs.TOTAL);
get amount(): EncapsulatedElementType {
return encapsulated({
detox: () => Matchers.getElementByID(ConfirmationRowComponentIDs.AMOUNT),
appium: () =>
PlaywrightMatchers.getElementById(ConfirmationRowComponentIDs.AMOUNT, {
exact: true,
}),
});
}

get transactionFee(): DetoxElement {
return Matchers.getElementByID(ConfirmationRowComponentIDs.TRANSACTION_FEE);
get total(): EncapsulatedElementType {
return encapsulated({
detox: () => Matchers.getElementByID(ConfirmationRowComponentIDs.TOTAL),
appium: () =>
PlaywrightMatchers.getElementById(ConfirmationRowComponentIDs.TOTAL, {
exact: true,
}),
});
}

async tapPayWithRow(): Promise<void> {
await Gestures.waitAndTap(this.payWithRow, {
elemDescription: 'Pay With Row',
get transactionFee(): EncapsulatedElementType {
return encapsulated({
detox: () =>
Matchers.getElementByID(ConfirmationRowComponentIDs.TRANSACTION_FEE),
appium: () =>
PlaywrightMatchers.getElementById(
ConfirmationRowComponentIDs.TRANSACTION_FEE,
{
exact: true,
},
),
});
}

get tokenListScrollView(): Promise<Detox.NativeMatcher> {
get payWithTokenList(): EncapsulatedElementType {
return encapsulated({
detox: () =>
Matchers.getElementByID(TransactionPayComponentIDs.PAY_WITH_TOKEN_LIST),
appium: () =>
PlaywrightMatchers.getElementById(
TransactionPayComponentIDs.PAY_WITH_TOKEN_LIST,
{
exact: true,
},
),
});
}

get tokenListScrollViewIdentifier(): Promise<Detox.NativeMatcher> {
return Matchers.getIdentifier(
TransactionPayComponentIDs.PAY_WITH_TOKEN_LIST,
);
}

async tapPayWithToken(tokenSymbol: string): Promise<void> {
const tokenElement = Matchers.getElementByText(
tokenSymbol,
) as unknown as DetoxElement;
await Gestures.scrollToElement(tokenElement, this.tokenListScrollView, {
direction: 'down',
scrollAmount: 200,
elemDescription: `Pay With Token ${tokenSymbol}`,
private getTokenOptionAt(
tokenSymbol: string,
index: number,
): EncapsulatedElementType {
return encapsulated({
detox: () => Matchers.getElementByText(tokenSymbol, index),
appium: async () => {
const elements =
await PlaywrightMatchers.getAllElementsByText(tokenSymbol);
if (elements.length === 0) {
throw new Error(
`No pay with token option found for "${tokenSymbol}"`,
);
}
if (index >= elements.length) {
throw new Error(
`Token index ${index} out of bounds (${elements.length} elements)`,
);
}
return elements[index];
},
});
}

private getKeypadButton(key: string): EncapsulatedElementType {
return encapsulated({
detox: () => Matchers.getElementByText(key),
appium: () => PlaywrightMatchers.getElementByText(key),
});
await Gestures.waitAndTap(Matchers.getElementByText(tokenSymbol), {
elemDescription: `Pay With Token ${tokenSymbol}`,
}

private async expectText(
elem: EncapsulatedElementType,
text: string,
description: string,
): Promise<void> {
await encapsulatedAction({
detox: async () => {
await Assertions.expectElementToHaveText(asDetoxElement(elem), text, {
description,
});
},
appium: async () => {
const resolved = await asPlaywrightElement(elem);
const actualText = (await resolved.textContent())
.replace(/\s+/gu, ' ')
.trim();
Comment thread
cortisiko marked this conversation as resolved.
if (!actualText.includes(text)) {
throw new Error(
`${description}: expected text containing "${text}" but got "${actualText}"`,
);
}
},
Comment thread
cortisiko marked this conversation as resolved.
});
}

async tapPayWithRow(): Promise<void> {
await UnifiedGestures.waitAndTap(this.payWithRow, {
description: 'Pay With Row',
});
}

async isAmountEntryVisible(): Promise<void> {
await Assertions.expectElementToBeVisible(this.keyboardContinueButton, {
description: 'Transaction pay keyboard continue button',
timeout: 15000,
});
}

async isPayWithTokenListVisible(): Promise<void> {
await Assertions.expectElementToBeVisible(this.payWithTokenList, {
description: 'Pay with token list',
timeout: 15000,
});
}

async tapPayWithToken(tokenSymbol: string, index = 0): Promise<void> {
const tokenElement = this.getTokenOptionAt(tokenSymbol, index);
const opts = { description: `Pay With Token ${tokenSymbol}` };

if (FrameworkDetector.isDetox()) {
await UnifiedGestures.scrollToElement(
tokenElement,
this.tokenListScrollViewIdentifier,
{ ...opts, direction: 'down', scrollAmount: 200 },
);
}
await UnifiedGestures.waitAndTap(tokenElement, opts);
}

async tapKeyboardContinueButton(): Promise<void> {
await Gestures.waitAndTap(this.keyboardContinueButton, {
elemDescription: 'Keyboard Continue Button',
await UnifiedGestures.waitAndTap(this.keyboardContinueButton, {
description: 'Keyboard Continue Button',
});
}

async tapKeyboardAmount(amount: string): Promise<void> {
for (const char of amount) {
await Gestures.waitAndTap(Matchers.getElementByText(char), {
elemDescription: `Keyboard Key ${char}`,
await UnifiedGestures.waitAndTap(this.getKeypadButton(char), {
description: `Keyboard Key ${char}`,
});
}
}
Expand All @@ -86,20 +266,33 @@ class TransactionPayConfirmation {
}

async verifyBridgeTime(time: string): Promise<void> {
await Assertions.expectElementToHaveText(this.bridgeTime, time, {
description: 'Bridge time should be correct',
});
await this.expectText(
this.bridgeTime,
time,
'Bridge time should be correct',
);
}

async verifyAmount(amount: string): Promise<void> {
await this.expectText(this.amount, amount, 'Amount should be correct');
}

async verifyTotal(total: string): Promise<void> {
await Assertions.expectElementToHaveText(this.total, total, {
description: 'Total should be correct',
});
await this.expectText(this.total, total, 'Total should be correct');
}

async verifyTransactionFee(fee: string): Promise<void> {
await Assertions.expectElementToHaveText(this.transactionFee, fee, {
description: 'Transaction fee should be correct',
await this.expectText(
this.transactionFee,
fee,
'Transaction fee should be correct',
);
}

async verifyTransactionFeeVisible(): Promise<void> {
await Assertions.expectElementToBeVisible(this.transactionFee, {
description: 'Transaction fee row should be visible',
timeout: 15000,
});
}
}
Expand Down
Loading
Loading