From 354f88eb5d5ac20c19b43256b344c7a7189f3bbe Mon Sep 17 00:00:00 2001 From: Abdurrahman SASTIM Date: Fri, 17 Jan 2025 18:59:43 +0100 Subject: [PATCH] test: first detox speculos delegate tests --- apps/ledger-live-mobile/e2e/models/send.ts | 6 +- apps/ledger-live-mobile/e2e/models/stake.ts | 60 +++++++++++++ .../e2e/page/accounts/account.page.ts | 13 ++- apps/ledger-live-mobile/e2e/page/index.ts | 2 + .../e2e/page/speculos.page.ts | 12 ++- .../e2e/page/trade/deviceValidation.page.ts | 26 ++++++ .../e2e/page/trade/operationDetails.page.ts | 32 +++++++ .../e2e/page/trade/send.page.ts | 14 --- .../e2e/page/trade/stake.page.ts | 87 +++++++++++++------ .../e2e/specs/delegate/cosmos.spec.ts | 17 ++-- .../e2e/specs/speculos/delegate/delegate.ts | 55 ++++++++++++ .../speculos/delegate/delegateATOM.spec.ts | 6 ++ .../speculos/delegate/delegateNEAR.spec.ts | 6 ++ .../speculos/delegate/delegateSOL.spec.ts | 6 ++ .../e2e/specs/speculos/send/send.ts | 1 + .../src/components/StepHeader.tsx | 3 +- .../src/components/ValidateOnDevice.tsx | 8 +- .../cosmos/TransactionConfirmFields.tsx | 8 +- .../src/families/cosmos/operationDetails.tsx | 12 ++- .../cosmos/shared/02-SelectAmount.tsx | 3 +- .../families/near/StakingFlow/01-Started.tsx | 2 +- .../families/near/StakingFlow/02-Summary.tsx | 54 +++++++----- .../src/families/near/operationDetails.tsx | 6 +- .../families/near/shared/02-SelectAmount.tsx | 2 + .../solana/DelegationFlow/SelectAmount.tsx | 2 + .../solana/DelegationFlow/Started.tsx | 1 + .../solana/DelegationFlow/Summary.tsx | 69 ++++++++++----- .../src/families/solana/operationDetails.tsx | 2 + .../src/screens/OperationDetails/Content.tsx | 6 +- .../QuickAction/QuickActionButton/index.tsx | 6 +- 30 files changed, 413 insertions(+), 114 deletions(-) create mode 100644 apps/ledger-live-mobile/e2e/models/stake.ts create mode 100644 apps/ledger-live-mobile/e2e/page/trade/deviceValidation.page.ts create mode 100644 apps/ledger-live-mobile/e2e/specs/speculos/delegate/delegate.ts create mode 100644 apps/ledger-live-mobile/e2e/specs/speculos/delegate/delegateATOM.spec.ts create mode 100644 apps/ledger-live-mobile/e2e/specs/speculos/delegate/delegateNEAR.spec.ts create mode 100644 apps/ledger-live-mobile/e2e/specs/speculos/delegate/delegateSOL.spec.ts diff --git a/apps/ledger-live-mobile/e2e/models/send.ts b/apps/ledger-live-mobile/e2e/models/send.ts index 371523bf66e0..72d17776f2d0 100644 --- a/apps/ledger-live-mobile/e2e/models/send.ts +++ b/apps/ledger-live-mobile/e2e/models/send.ts @@ -30,14 +30,14 @@ export async function verifyAppValidationSendInfo( const addressSender = transaction.accountToDebit.address; if (currenciesForValidationAmount.includes(currency)) { - await app.send.expectValidationAmount(amount); + await app.deviceValidation.expectAmount(amount); } if (currenciesForValidationRecipient.includes(currency)) { - await app.send.expectValidationAddress(addressRecipient); + await app.deviceValidation.expectAddress(addressRecipient); } if (currenciesForValidationSender.includes(currency)) { - await app.send.expectValidationAddress(addressSender); + await app.deviceValidation.expectAddress(addressSender); } } diff --git a/apps/ledger-live-mobile/e2e/models/stake.ts b/apps/ledger-live-mobile/e2e/models/stake.ts new file mode 100644 index 000000000000..af638f67d820 --- /dev/null +++ b/apps/ledger-live-mobile/e2e/models/stake.ts @@ -0,0 +1,60 @@ +import { Currency } from "@ledgerhq/live-common/e2e/enum/Currency"; +import { Application } from "../page"; +import { Delegate } from "@ledgerhq/live-common/e2e/models/Delegate"; + +export async function verifyAppValidationStakeInfo( + app: Application, + delegation: Delegate, + amount: string, +) { + const currenciesForValidationAmount = [Currency.ATOM, Currency.NEAR]; + const currenciesForValidationProvider = [Currency.ATOM]; + + const currency = delegation.account.currency; + const provider = delegation.provider; + + if (currenciesForValidationAmount.includes(currency)) { + await app.deviceValidation.expectAmount(amount); + } + if (currenciesForValidationProvider.includes(currency)) { + await app.deviceValidation.expectProvider(provider); + } +} + +export async function verifyStakeOperationDetailsInfo( + app: Application, + delegation: Delegate, + amount: string, +) { + const currenciesForProvider = [Currency.ATOM]; + const currenciesForRecipientAsProvider = [Currency.NEAR]; + const currenciesForSender = [Currency.NEAR]; + const currenciesForAmount = [Currency.ATOM, Currency.NEAR]; + const currenciesForDelegateType = [Currency.ATOM, Currency.SOL]; + const currenciesForStakeType = [Currency.NEAR]; + + const currency = delegation.account.currency; + const provider = delegation.provider; + + await app.operationDetails.waitForOperationDetails(); + await app.operationDetails.checkAccount(delegation.account.accountName); + + if (currenciesForAmount.includes(currency)) { + await app.operationDetails.checkDelegatedAmount(amount); + } + if (currenciesForProvider.includes(currency)) { + await app.operationDetails.checkProvider(provider); + } + if (currenciesForRecipientAsProvider.includes(currency)) { + await app.operationDetails.checkRecipient(provider); + } + if (currenciesForSender.includes(currency)) { + await app.operationDetails.checkSender(delegation.account.address); + } + if (currenciesForDelegateType.includes(currency)) { + await app.operationDetails.checkTransactionType("DELEGATE"); + } + if (currenciesForStakeType.includes(currency)) { + await app.operationDetails.checkTransactionType("STAKE"); + } +} diff --git a/apps/ledger-live-mobile/e2e/page/accounts/account.page.ts b/apps/ledger-live-mobile/e2e/page/accounts/account.page.ts index cb9e6d5e7f70..52bde820c07d 100644 --- a/apps/ledger-live-mobile/e2e/page/accounts/account.page.ts +++ b/apps/ledger-live-mobile/e2e/page/accounts/account.page.ts @@ -1,4 +1,4 @@ -import { getElementById, getTextOfElement, scrollToId, tapByElement } from "../../helpers"; +import { getElementById, getTextOfElement, scrollToId, tapByElement, tapById } from "../../helpers"; import { expect } from "detox"; import jestExpect from "expect"; @@ -14,8 +14,9 @@ export default class AccountPage { operationHistorySectionId = (accountId: string) => this.operationHistorySection + accountId; accountScreenScrollView = "account-screen-scrollView"; accountAdvancedLogsId = "account-advanced-logs"; - receiveButton = () => getElementById("account-quick-action-button-Receive"); - sendButton = () => getElementById("account-quick-action-button-Send"); + receiveButton = () => getElementById("account-quick-action-button-receive"); + sendButton = () => getElementById("account-quick-action-button-send"); + earnButtonId = "account-quick-action-button-earn"; @Step("Open account settings") async openAccountSettings() { @@ -73,4 +74,10 @@ export default class AccountPage { async tapSend() { await tapByElement(this.sendButton()); } + + @Step("Tap on earn button") + async tapEarn() { + await scrollToId(this.earnButtonId, this.accountScreenScrollView); + await tapById(this.earnButtonId); + } } diff --git a/apps/ledger-live-mobile/e2e/page/index.ts b/apps/ledger-live-mobile/e2e/page/index.ts index dad053ccff10..7eb108e1d94e 100644 --- a/apps/ledger-live-mobile/e2e/page/index.ts +++ b/apps/ledger-live-mobile/e2e/page/index.ts @@ -6,6 +6,7 @@ import BuyDevicePage from "./discover/buyDevice.page"; import CommonPage from "./common.page"; import CryptoDrawer from "./liveApps/cryptoDrawer"; import CustomLockscreenPage from "./stax/customLockscreen.page"; +import DeviceValidationPage from "./trade/deviceValidation.page"; import DiscoverPage from "./discover/discover.page"; import DummyWalletApp from "./liveApps/dummyWalletApp.webView"; import WalletAPIReceivePage from "./liveApps/walletAPIReceive"; @@ -60,6 +61,7 @@ export class Application { public common = new CommonPage(); public cryptoDrawer = new CryptoDrawer(); public customLockscreen = new CustomLockscreenPage(); + public deviceValidation = new DeviceValidationPage(); public discover = new DiscoverPage(); public dummyWalletApp = new DummyWalletApp(); public walletAPIReceive = new WalletAPIReceivePage(); diff --git a/apps/ledger-live-mobile/e2e/page/speculos.page.ts b/apps/ledger-live-mobile/e2e/page/speculos.page.ts index 3525ebac220f..7cd8cfc21040 100644 --- a/apps/ledger-live-mobile/e2e/page/speculos.page.ts +++ b/apps/ledger-live-mobile/e2e/page/speculos.page.ts @@ -1,6 +1,11 @@ -import { expectValidAddressDevice, signSendTransaction } from "@ledgerhq/live-common/e2e/speculos"; +import { + expectValidAddressDevice, + signSendTransaction, + signDelegationTransaction, +} from "@ledgerhq/live-common/e2e/speculos"; import { Account } from "@ledgerhq/live-common/e2e/enum/Account"; import { Transaction } from "@ledgerhq/live-common/e2e/models/Transaction"; +import { Delegate } from "@ledgerhq/live-common/e2e/models/Delegate"; export default class SpeculosPage { @Step("Verify receive address correctness on device") @@ -12,4 +17,9 @@ export default class SpeculosPage { async signSendTransaction(tx: Transaction) { await signSendTransaction(tx); } + + @Step("Sign Delegation Transaction") + async signDelegationTransaction(delegation: Delegate) { + await signDelegationTransaction(delegation); + } } diff --git a/apps/ledger-live-mobile/e2e/page/trade/deviceValidation.page.ts b/apps/ledger-live-mobile/e2e/page/trade/deviceValidation.page.ts new file mode 100644 index 000000000000..a485a2fb7992 --- /dev/null +++ b/apps/ledger-live-mobile/e2e/page/trade/deviceValidation.page.ts @@ -0,0 +1,26 @@ +import { getElementById, waitForElementById } from "../../helpers"; +import { expect } from "detox"; + +export default class DeviceValidationPage { + validationAmountId = "device-validation-amount"; + validationAddressId = (fieldLabel = "To") => `device-validation-address${fieldLabel}`; + validationProviderId = "device-validation-provider"; + + @Step("Expect amount in device validation screen") + async expectAmount(amount: string) { + await waitForElementById(this.validationAmountId); + await expect(getElementById(this.validationAmountId)).toHaveText(amount); + } + + @Step("Expect address in device validation screen") + async expectAddress(recipient: string, fieldLabel?: string) { + await waitForElementById(this.validationAddressId(fieldLabel)); + await expect(getElementById(this.validationAddressId(fieldLabel))).toHaveText(recipient); + } + + @Step("Expect provider in device validation screen") + async expectProvider(provider: string) { + await waitForElementById(this.validationProviderId); + await expect(getElementById(this.validationProviderId)).toHaveText(provider); + } +} diff --git a/apps/ledger-live-mobile/e2e/page/trade/operationDetails.page.ts b/apps/ledger-live-mobile/e2e/page/trade/operationDetails.page.ts index 078a5aaa5b5a..dd44aad48d96 100644 --- a/apps/ledger-live-mobile/e2e/page/trade/operationDetails.page.ts +++ b/apps/ledger-live-mobile/e2e/page/trade/operationDetails.page.ts @@ -7,6 +7,15 @@ export default class OperationDetailsPage { account = () => getElementById("operationDetails-account"); amount = () => getElementById("operationDetails-amount"); recipientId = "operationDetails-recipient0"; + delegatedAmountId = "operationDetails-delegatedAmount"; + providerId = "operationDetails-delegatedTo"; + senderId = "operationDetails-sender0"; + + "operationsType" = { + OUT: "Sent", + DELEGATE: "Delegated", + STAKE: "Staked", + }; async isOpened() { await expect(this.title()).toBeVisible(); @@ -31,4 +40,27 @@ export default class OperationDetailsPage { await scrollToId(this.recipientId); await expect(getElementById(this.recipientId)).toHaveText(recipient); } + + @Step("Check delegated provider") + async checkProvider(provider: string) { + await scrollToId(this.providerId); + await expect(getElementById(this.providerId)).toHaveText(provider); + } + + @Step("Check delegated amount") + async checkDelegatedAmount(amount: string) { + await scrollToId(this.delegatedAmountId); + await expect(getElementById(this.delegatedAmountId)).toHaveText(amount); + } + + @Step("Check sender") + async checkSender(sender: string) { + await scrollToId(this.senderId); + await expect(getElementById(this.senderId)).toHaveText(sender); + } + + @Step("Check transaction type") + async checkTransactionType(type: keyof typeof this.operationsType) { + await expect(getElementById(this.titleId)).toHaveText(this.operationsType[type]); + } } diff --git a/apps/ledger-live-mobile/e2e/page/trade/send.page.ts b/apps/ledger-live-mobile/e2e/page/trade/send.page.ts index 053a67f95e4d..aafe3f709264 100644 --- a/apps/ledger-live-mobile/e2e/page/trade/send.page.ts +++ b/apps/ledger-live-mobile/e2e/page/trade/send.page.ts @@ -15,8 +15,6 @@ const baseLink = "send"; export default class SendPage { summaryAmount = () => getElementById("send-summary-amount"); summaryRecipient = () => getElementById("send-summary-recipient"); - validationAmountId = "send-validation-amount"; - validationAddressId = "send-validation-address"; getStep1HeaderTitle = () => getElementById("send-header-step1-title"); recipientContinueButtonId = "recipient-continue-button"; recipientInputId = "recipient-input"; @@ -88,16 +86,4 @@ export default class SendPage { if (await IsIdVisible(this.highFreeConfirmButtonID)) await tapById(this.highFreeConfirmButtonID); } - - @Step("Expect amount in device validation screen") - async expectValidationAmount(amount: string) { - await waitForElementById(this.validationAmountId); - await expect(getElementById(this.validationAmountId)).toHaveText(amount); - } - - @Step("Expect address in device validation screen") - async expectValidationAddress(recipient: string) { - await waitForElementById(this.validationAddressId); - await expect(getElementById(this.validationAddressId)).toHaveText(recipient); - } } diff --git a/apps/ledger-live-mobile/e2e/page/trade/stake.page.ts b/apps/ledger-live-mobile/e2e/page/trade/stake.page.ts index b6257c5f352b..b39f9f6f9f7f 100644 --- a/apps/ledger-live-mobile/e2e/page/trade/stake.page.ts +++ b/apps/ledger-live-mobile/e2e/page/trade/stake.page.ts @@ -1,16 +1,27 @@ -import { getTextOfElement, tapById, waitForElementById } from "../../helpers"; +import { + getTextOfElement, + IsIdVisible, + tapById, + typeTextById, + waitForElementById, +} from "../../helpers"; export default class StakePage { - cosmosDelegationSummaryValidatorId = "cosmos-delegation-summary-validator"; - cosmosDelegationSummaryValidator = () => getTextOfElement("cosmos-delegation-summary-validator"); - cosmosDelegationSummaryAmountId = "cosmos-delegation-summary-amount"; - cosmosDelegationAmountValue = () => getTextOfElement(this.cosmosDelegationSummaryAmountId); - cosmosAssestsRemainingId = "cosmos-assets-remaining"; - cosmosDelegatedRatioId = (delegatedPercent: number) => `delegate-ratio-${delegatedPercent}%`; - cosmosAllAssestsUsedText = "cosmos-all-assets-used-text"; - cosmosSummaryContinueButtonId = "cosmos-summary-continue-button"; - cosmosDelegationStartId = "cosmos-delegation-start-button"; - cosmosDelegationAmountContinueId = "cosmos-delegation-amount-continue"; + delegationSummaryValidatorId = (currencyId: string) => + `${currencyId}-delegation-summary-validator`; + delegationSummaryValidator = (currencyId: string) => + getTextOfElement(`${currencyId}-delegation-summary-validator`); + delegationSummaryAmountId = (currencyId: string) => `${currencyId}-delegation-summary-amount`; + delegationAmountValue = (currencyId: string) => + getTextOfElement(this.delegationSummaryAmountId(currencyId)); + assestsRemainingId = (currencyId: string) => `${currencyId}-assets-remaining`; + delegatedRatioId = (currencyId: string, delegatedPercent: number) => + `${currencyId}-delegate-ratio-${delegatedPercent}%`; + delegationAmountInput = (currencyId: string) => `${currencyId}-delegation-amount-input`; + allAssestsUsedText = (currencyId: string) => `${currencyId}-all-assets-used-text`; + summaryContinueButtonId = (currencyId: string) => `${currencyId}-summary-continue-button`; + delegationStartId = (currencyId: string) => `${currencyId}-delegation-start-button`; + delegationAmountContinueId = (currencyId: string) => `${currencyId}-delegation-amount-continue`; currencyRow = (currencyId: string) => `currency-row-${currencyId}`; zeroAssetText = "0\u00a0ATOM"; @@ -20,44 +31,64 @@ export default class StakePage { await tapById(id); } - async delegationStart() { - await tapById(this.cosmosDelegationStartId); - await waitForElementById(this.cosmosDelegationSummaryValidatorId); + @Step("Click on start delegation button") + async delegationStart(currencyId: string) { + await tapById(this.delegationStartId(currencyId)); + await waitForElementById(this.delegationSummaryValidatorId(currencyId)); } - async setAmount(delegatedPercent: 25 | 50 | 75 | 100) { - await waitForElementById(this.cosmosDelegationSummaryAmountId); - await tapById(this.cosmosDelegationSummaryAmountId); - await tapById(this.cosmosDelegatedRatioId(delegatedPercent)); + @Step("Dismiss delegation start page if displayed") + async dismissDelegationStart(currencyId: string) { + if (await IsIdVisible(this.delegationStartId(currencyId))) { + await this.delegationStart(currencyId); + } } - async expectValidator(validator: string) { - expect(await this.cosmosDelegationSummaryValidator()).toEqual(validator); + @Step("Set delegated amount") + async setAmount(currencyId: string, amount: string) { + await waitForElementById(this.delegationSummaryAmountId(currencyId)); + await tapById(this.delegationSummaryAmountId(currencyId)); + await typeTextById(this.delegationAmountInput(currencyId), amount); + } + + async setAmountPercent(currencyId: string, delegatedPercent: 25 | 50 | 75 | 100) { + await waitForElementById(this.delegationSummaryAmountId(currencyId)); + await tapById(this.delegationSummaryAmountId(currencyId)); + await tapById(this.delegatedRatioId(currencyId, delegatedPercent)); + } + + @Step("Expect provider in summary") + async expectProvider(currencyId: string, provider: string) { + expect(await this.delegationSummaryValidator(currencyId)).toEqual(provider); } async expectRemainingAmount( + currencyId: string, delegatedPercent: 25 | 50 | 75 | 100, remainingAmountFormated: string, ) { const max = delegatedPercent == 100; - const id = max ? this.cosmosAllAssestsUsedText : this.cosmosAssestsRemainingId; + const id = max ? this.allAssestsUsedText(currencyId) : this.assestsRemainingId(currencyId); await waitForElementById(id); const assestsRemaining = max ? this.zeroAssetText : (await getTextOfElement(id)).split(": ")[1]; expect(assestsRemaining).toEqual(remainingAmountFormated); } - async validateAmount() { - await tapById(this.cosmosDelegationAmountContinueId); - await waitForElementById(this.cosmosDelegationSummaryAmountId); + @Step("Validate the amount entered") + async validateAmount(currencyId: string) { + await tapById(this.delegationAmountContinueId(currencyId)); + await waitForElementById(this.delegationSummaryAmountId(currencyId)); } - async expectDelegatedAmount(delegatedAmountFormated: string) { - const assestsDelagated = await this.cosmosDelegationAmountValue(); + @Step("Expect delegated amount in summary") + async expectDelegatedAmount(currencyId: string, delegatedAmountFormated: string) { + const assestsDelagated = await this.delegationAmountValue(currencyId); expect(assestsDelagated).toEqual(delegatedAmountFormated); } - async summaryContinue() { - await tapById(this.cosmosSummaryContinueButtonId); + @Step("Click on continue button in summary") + async summaryContinue(currencyId: string) { + await tapById(this.summaryContinueButtonId(currencyId)); } } diff --git a/apps/ledger-live-mobile/e2e/specs/delegate/cosmos.spec.ts b/apps/ledger-live-mobile/e2e/specs/delegate/cosmos.spec.ts index cb63c896dcc3..ff3f5cc34428 100644 --- a/apps/ledger-live-mobile/e2e/specs/delegate/cosmos.spec.ts +++ b/apps/ledger-live-mobile/e2e/specs/delegate/cosmos.spec.ts @@ -14,7 +14,7 @@ const app = new Application(); let deviceAction: DeviceAction; const testedCurrency = "cosmos"; -const defaultValidator = "Ledger"; +const defaultProvider = "Ledger"; const testAccount = initTestAccounts([testedCurrency])[0]; const knownDevice = knownDevices.nanoX; @@ -53,14 +53,19 @@ describe("Cosmos delegate flow", () => { await app.stake.selectCurrency(testedCurrency); await app.common.selectAccount(testAccount.id); - await app.stake.setAmount(delegatedPercent); - await app.stake.expectRemainingAmount(delegatedPercent, formattedAmount(unit, remainingAmount)); - await app.stake.validateAmount(); + await app.stake.setAmountPercent(testedCurrency, delegatedPercent); + await app.stake.expectRemainingAmount( + testedCurrency, + delegatedPercent, + formattedAmount(unit, remainingAmount), + ); + await app.stake.validateAmount(testedCurrency); await app.stake.expectDelegatedAmount( + testedCurrency, formattedAmount(unit, delegatedAmount, { showAllDigits: true, showCode: true }), ); - await app.stake.expectValidator(defaultValidator); - await app.stake.summaryContinue(); + await app.stake.expectProvider(testedCurrency, defaultProvider); + await app.stake.summaryContinue(testedCurrency); await deviceAction.selectMockDevice(); await deviceAction.openApp(); await app.common.successClose(); diff --git a/apps/ledger-live-mobile/e2e/specs/speculos/delegate/delegate.ts b/apps/ledger-live-mobile/e2e/specs/speculos/delegate/delegate.ts new file mode 100644 index 000000000000..c5a2ba919842 --- /dev/null +++ b/apps/ledger-live-mobile/e2e/specs/speculos/delegate/delegate.ts @@ -0,0 +1,55 @@ +import { Delegate } from "@ledgerhq/live-common/e2e/models/Delegate"; +import { + verifyAppValidationStakeInfo, + verifyStakeOperationDetailsInfo, +} from "../../../models/stake"; +import { Application } from "../../../page"; +import { CLI } from "../../../utils/cliUtils"; +import { device } from "detox"; + +export async function runDelegateTest(delegation: Delegate, tmsLink: string) { + const app = new Application(); + + $TmsLink(tmsLink); + describe(`Send flow on ${delegation.account.currency.name}`, () => { + beforeAll(async () => { + await app.init({ + speculosApp: delegation.account.currency.speculosApp, + cliCommands: [ + () => { + return CLI.liveData({ + currency: delegation.account.currency.currencyId, + index: delegation.account.index, + add: true, + appjson: app.userdataPath, + }); + }, + ], + }); + + await app.portfolio.waitForPortfolioPageToLoad(); + }); + + it(`Delegate on ${delegation.account.currency.name}`, async () => { + const amountWithCode = delegation.amount + " " + delegation.account.currency.ticker; + const currencyId = delegation.account.currency.currencyId; + + await app.accounts.openViaDeeplink(); + await app.common.goToAccountByName(delegation.account.accountName); + await app.account.tapEarn(); + + await app.stake.dismissDelegationStart(currencyId); + await app.stake.setAmount(currencyId, delegation.amount); + await app.stake.validateAmount(currencyId); + await app.stake.expectProvider(currencyId, delegation.provider); + await app.stake.summaryContinue(currencyId); + + await verifyAppValidationStakeInfo(app, delegation, amountWithCode); + await app.speculos.signDelegationTransaction(delegation); + await device.disableSynchronization(); + await app.common.successViewDetails(); + + await verifyStakeOperationDetailsInfo(app, delegation, amountWithCode); + }); + }); +} diff --git a/apps/ledger-live-mobile/e2e/specs/speculos/delegate/delegateATOM.spec.ts b/apps/ledger-live-mobile/e2e/specs/speculos/delegate/delegateATOM.spec.ts new file mode 100644 index 000000000000..6e9c8842713b --- /dev/null +++ b/apps/ledger-live-mobile/e2e/specs/speculos/delegate/delegateATOM.spec.ts @@ -0,0 +1,6 @@ +import { Account } from "@ledgerhq/live-common/e2e/enum/Account"; +import { runDelegateTest } from "./delegate"; +import { Delegate } from "@ledgerhq/live-common/e2e/models/Delegate"; + +const delegation = new Delegate(Account.ATOM_1, "0.001", "Ledger"); +runDelegateTest(delegation, "B2CQA-2740, B2CQA-2770"); diff --git a/apps/ledger-live-mobile/e2e/specs/speculos/delegate/delegateNEAR.spec.ts b/apps/ledger-live-mobile/e2e/specs/speculos/delegate/delegateNEAR.spec.ts new file mode 100644 index 000000000000..1a12f5abc845 --- /dev/null +++ b/apps/ledger-live-mobile/e2e/specs/speculos/delegate/delegateNEAR.spec.ts @@ -0,0 +1,6 @@ +import { Account } from "@ledgerhq/live-common/e2e/enum/Account"; +import { runDelegateTest } from "./delegate"; +import { Delegate } from "@ledgerhq/live-common/e2e/models/Delegate"; + +const delegation = new Delegate(Account.NEAR_1, "0.01", "ledgerbyfigment.poolv1.near"); +runDelegateTest(delegation, "B2CQA-2741"); diff --git a/apps/ledger-live-mobile/e2e/specs/speculos/delegate/delegateSOL.spec.ts b/apps/ledger-live-mobile/e2e/specs/speculos/delegate/delegateSOL.spec.ts new file mode 100644 index 000000000000..62413e45fd72 --- /dev/null +++ b/apps/ledger-live-mobile/e2e/specs/speculos/delegate/delegateSOL.spec.ts @@ -0,0 +1,6 @@ +import { Account } from "@ledgerhq/live-common/e2e/enum/Account"; +import { runDelegateTest } from "./delegate"; +import { Delegate } from "@ledgerhq/live-common/e2e/models/Delegate"; + +const delegation = new Delegate(Account.SOL_1, "0.001", "Ledger by Figment"); +runDelegateTest(delegation, "B2CQA-2742"); diff --git a/apps/ledger-live-mobile/e2e/specs/speculos/send/send.ts b/apps/ledger-live-mobile/e2e/specs/speculos/send/send.ts index 9064e49ffa00..d9bd8c62cd0f 100644 --- a/apps/ledger-live-mobile/e2e/specs/speculos/send/send.ts +++ b/apps/ledger-live-mobile/e2e/specs/speculos/send/send.ts @@ -60,6 +60,7 @@ export async function runSendTest(transaction: Transaction, tmsLink: string) { await app.operationDetails.waitForOperationDetails(); await app.operationDetails.checkAccount(transaction.accountToDebit.accountName); await app.operationDetails.checkRecipient(transaction.accountToCredit.address); + await app.operationDetails.checkTransactionType("OUT"); }); }); } diff --git a/apps/ledger-live-mobile/src/components/StepHeader.tsx b/apps/ledger-live-mobile/src/components/StepHeader.tsx index 819bdb2e233a..d32fd3d4b253 100644 --- a/apps/ledger-live-mobile/src/components/StepHeader.tsx +++ b/apps/ledger-live-mobile/src/components/StepHeader.tsx @@ -12,7 +12,7 @@ type Props = { export default function StepHeader({ title, subtitle, testID }: Props) { return ( - + {subtitle && ( {title} diff --git a/apps/ledger-live-mobile/src/components/ValidateOnDevice.tsx b/apps/ledger-live-mobile/src/components/ValidateOnDevice.tsx index 44b79de4f3a2..76a3b1b2ec5c 100644 --- a/apps/ledger-live-mobile/src/components/ValidateOnDevice.tsx +++ b/apps/ledger-live-mobile/src/components/ValidateOnDevice.tsx @@ -49,7 +49,7 @@ function AmountField({ account, status, field }: FieldComponentProps) { label={field.label} unit={unit} value={status.amount} - testID="send-validation-amount" + testID="device-validation-amount" /> ); } @@ -65,7 +65,11 @@ function FeesField({ account, parentAccount, status, field }: FieldComponentProp function AddressField({ field }: FieldComponentProps) { invariant(field.type === "address", "AddressField invalid"); return ( - + ); } diff --git a/apps/ledger-live-mobile/src/families/cosmos/TransactionConfirmFields.tsx b/apps/ledger-live-mobile/src/families/cosmos/TransactionConfirmFields.tsx index daa4c1e5cf58..604ae722d08c 100644 --- a/apps/ledger-live-mobile/src/families/cosmos/TransactionConfirmFields.tsx +++ b/apps/ledger-live-mobile/src/families/cosmos/TransactionConfirmFields.tsx @@ -31,13 +31,17 @@ function CosmosDelegateValidatorsField({ account, transaction }: FieldProps) { const { validator, formattedAmount, address } = mappedDelegations[0]; return ( <> - + {shortAddressPreview(address)} - + {validator?.name ?? null} diff --git a/apps/ledger-live-mobile/src/families/cosmos/operationDetails.tsx b/apps/ledger-live-mobile/src/families/cosmos/operationDetails.tsx index 3e3c01f651ee..9901d6d29422 100644 --- a/apps/ledger-live-mobile/src/families/cosmos/operationDetails.tsx +++ b/apps/ledger-live-mobile/src/families/cosmos/operationDetails.tsx @@ -74,8 +74,16 @@ function OperationDetailsExtra({ operation, type, account }: Props) { }); ret = ( <> -
-
+
+
); break; diff --git a/apps/ledger-live-mobile/src/families/cosmos/shared/02-SelectAmount.tsx b/apps/ledger-live-mobile/src/families/cosmos/shared/02-SelectAmount.tsx index 9ed43ebc8620..c7a6070be294 100644 --- a/apps/ledger-live-mobile/src/families/cosmos/shared/02-SelectAmount.tsx +++ b/apps/ledger-live-mobile/src/families/cosmos/shared/02-SelectAmount.tsx @@ -153,6 +153,7 @@ function DelegationAmount({ navigation, route }: Props) { onChange={setValue} inputStyle={styles.inputStyle} hasError={isAmountOutOfRange || isNotEnoughBalance} + testID="cosmos-delegation-amount-input" /> {ratioButtons.map(({ label, value: v }) => ( @@ -176,7 +177,7 @@ function DelegationAmount({ navigation, route }: Props) { {label} diff --git a/apps/ledger-live-mobile/src/families/near/StakingFlow/01-Started.tsx b/apps/ledger-live-mobile/src/families/near/StakingFlow/01-Started.tsx index 95073d6f92a8..e31c16fbafb0 100644 --- a/apps/ledger-live-mobile/src/families/near/StakingFlow/01-Started.tsx +++ b/apps/ledger-live-mobile/src/families/near/StakingFlow/01-Started.tsx @@ -75,7 +75,7 @@ export default function StakingStarted({ navigation, route }: Props) { - diff --git a/apps/ledger-live-mobile/src/families/near/StakingFlow/02-Summary.tsx b/apps/ledger-live-mobile/src/families/near/StakingFlow/02-Summary.tsx index 731d038d12d1..dbee68410656 100644 --- a/apps/ledger-live-mobile/src/families/near/StakingFlow/02-Summary.tsx +++ b/apps/ledger-live-mobile/src/families/near/StakingFlow/02-Summary.tsx @@ -33,6 +33,7 @@ import { getFirstStatusError, hasStatusError } from "../../helpers"; import type { BaseComposite, StackNavigatorProps } from "~/components/RootNavigator/types/helpers"; import type { NearStakingFlowParamList } from "./types"; import { useAccountUnit } from "~/hooks/useAccountUnit"; +import Config from "react-native-config"; type Props = BaseComposite< StackNavigatorProps @@ -104,26 +105,28 @@ export default function StakingSummary({ navigation, route }: Props) { const [rotateAnim] = useState(() => new Animated.Value(0)); useEffect(() => { - Animated.loop( - Animated.sequence([ - Animated.timing(rotateAnim, { - toValue: 1, - duration: 200, - useNativeDriver: true, - }), - Animated.timing(rotateAnim, { - toValue: -1, - duration: 300, - useNativeDriver: true, - }), - Animated.timing(rotateAnim, { - toValue: 0, - duration: 200, - useNativeDriver: true, - }), - Animated.delay(1000), - ]), - ).start(); + if (!Config.DETOX) { + Animated.loop( + Animated.sequence([ + Animated.timing(rotateAnim, { + toValue: 1, + duration: 200, + useNativeDriver: true, + }), + Animated.timing(rotateAnim, { + toValue: -1, + duration: 300, + useNativeDriver: true, + }), + Animated.timing(rotateAnim, { + toValue: 0, + duration: 200, + useNativeDriver: true, + }), + Animated.delay(1000), + ]), + ).start(); + } return () => { rotateAnim.setValue(0); }; @@ -241,6 +244,7 @@ export default function StakingSummary({ navigation, route }: Props) { onPress={onContinue} disabled={bridgePending || !!bridgeError || hasErrors} pending={bridgePending} + testID="near-summary-continue-button" /> @@ -358,7 +362,7 @@ function SummaryWords({ - + @@ -366,7 +370,10 @@ function SummaryWords({ - + @@ -422,7 +429,7 @@ const Words = ({ ); -const Selectable = ({ name }: { name: string; readOnly?: boolean }) => { +const Selectable = ({ name, testID }: { name: string; readOnly?: boolean; testID: string }) => { const { colors } = useTheme(); return ( @@ -431,6 +438,7 @@ const Selectable = ({ name }: { name: string; readOnly?: boolean }) => { numberOfLines={1} style={styles.validatorSelectionText} color={colors.primary} + testID={testID} > {name} diff --git a/apps/ledger-live-mobile/src/families/near/operationDetails.tsx b/apps/ledger-live-mobile/src/families/near/operationDetails.tsx index f314bf8b4490..215b62357a3d 100644 --- a/apps/ledger-live-mobile/src/families/near/operationDetails.tsx +++ b/apps/ledger-live-mobile/src/families/near/operationDetails.tsx @@ -34,7 +34,11 @@ function OperationDetailsExtra({ account, operation }: Props) { return ( <> -
+
); } diff --git a/apps/ledger-live-mobile/src/families/near/shared/02-SelectAmount.tsx b/apps/ledger-live-mobile/src/families/near/shared/02-SelectAmount.tsx index e2a42e990534..1ebb27fd22a7 100644 --- a/apps/ledger-live-mobile/src/families/near/shared/02-SelectAmount.tsx +++ b/apps/ledger-live-mobile/src/families/near/shared/02-SelectAmount.tsx @@ -116,6 +116,7 @@ function StakingAmount({ navigation, route }: Props) { inputStyle={styles.inputStyle} hasError={!!error} hasWarning={!!warning} + testID="near-delegation-amount-input" /> } type="primary" + testID="near-delegation-amount-continue" /> diff --git a/apps/ledger-live-mobile/src/families/solana/DelegationFlow/SelectAmount.tsx b/apps/ledger-live-mobile/src/families/solana/DelegationFlow/SelectAmount.tsx index e81b587851f1..5f5ba67035e8 100644 --- a/apps/ledger-live-mobile/src/families/solana/DelegationFlow/SelectAmount.tsx +++ b/apps/ledger-live-mobile/src/families/solana/DelegationFlow/SelectAmount.tsx @@ -151,6 +151,7 @@ export default function DelegationSelectAmount({ navigation, route }: Props) { : status.errors.amount } warning={status.warnings.amount} + testID="solana-delegation-amount-input" /> @@ -204,6 +205,7 @@ export default function DelegationSelectAmount({ navigation, route }: Props) { /> } onPress={onContinue} + testID="solana-delegation-amount-continue" disabled={!!status.errors.amount || bridgePending} /> diff --git a/apps/ledger-live-mobile/src/families/solana/DelegationFlow/Started.tsx b/apps/ledger-live-mobile/src/families/solana/DelegationFlow/Started.tsx index 0182a4fd72af..f4a8a7bcf8c4 100644 --- a/apps/ledger-live-mobile/src/families/solana/DelegationFlow/Started.tsx +++ b/apps/ledger-live-mobile/src/families/solana/DelegationFlow/Started.tsx @@ -73,6 +73,7 @@ export default function DelegationStarted({ navigation, route }: Props) { onPress={onNext} title={} type="primary" + testID="solana-delegation-start-button" /> diff --git a/apps/ledger-live-mobile/src/families/solana/DelegationFlow/Summary.tsx b/apps/ledger-live-mobile/src/families/solana/DelegationFlow/Summary.tsx index 26a46914a4b0..5492d36da892 100644 --- a/apps/ledger-live-mobile/src/families/solana/DelegationFlow/Summary.tsx +++ b/apps/ledger-live-mobile/src/families/solana/DelegationFlow/Summary.tsx @@ -39,6 +39,7 @@ import TranslatedError from "../../../components/TranslatedError"; import { useAccountUnit } from "~/hooks/useAccountUnit"; import NotEnoughFundFeesAlert from "../../shared/StakingErrors/NotEnoughFundFeesAlert"; import { NotEnoughBalance } from "@ledgerhq/errors"; +import Config from "react-native-config"; type Props = StackNavigatorProps; @@ -101,26 +102,28 @@ export default function DelegationSummary({ navigation, route }: Props) { const [rotateAnim] = useState(() => new Animated.Value(0)); useEffect(() => { - Animated.loop( - Animated.sequence([ - Animated.timing(rotateAnim, { - toValue: 1, - duration: 200, - useNativeDriver: true, - }), - Animated.timing(rotateAnim, { - toValue: -1, - duration: 300, - useNativeDriver: true, - }), - Animated.timing(rotateAnim, { - toValue: 0, - duration: 200, - useNativeDriver: true, - }), - Animated.delay(1000), - ]), - ).start(); + if (!Config.DETOX) { + Animated.loop( + Animated.sequence([ + Animated.timing(rotateAnim, { + toValue: 1, + duration: 200, + useNativeDriver: true, + }), + Animated.timing(rotateAnim, { + toValue: -1, + duration: 300, + useNativeDriver: true, + }), + Animated.timing(rotateAnim, { + toValue: 0, + duration: 200, + useNativeDriver: true, + }), + Animated.delay(1000), + ]), + ).start(); + } return () => { rotateAnim.setValue(0); }; @@ -242,6 +245,7 @@ export default function DelegationSummary({ navigation, route }: Props) { onPress={onContinue} disabled={bridgePending || !!bridgeError || hasErrors} pending={bridgePending} + testID="solana-summary-continue-button" /> @@ -463,7 +467,7 @@ function SummaryWords({ {delegationAction.kind === "new" ? ( - + ) : ( @@ -479,7 +483,10 @@ function SummaryWords({ {delegationAction.kind === "new" || delegationAction.stakeAction === "activate" ? ( - + ) : ( @@ -538,11 +545,25 @@ const Words = ({ ); -const Selectable = ({ name, readOnly }: { name: string; readOnly?: boolean }) => { +const Selectable = ({ + name, + readOnly, + testID, +}: { + name: string; + readOnly?: boolean; + testID?: string; +}) => { const { colors } = useTheme(); return ( - + {name} {readOnly ? null : ( diff --git a/apps/ledger-live-mobile/src/families/solana/operationDetails.tsx b/apps/ledger-live-mobile/src/families/solana/operationDetails.tsx index 88d9c9e96706..7b9c828a0a83 100644 --- a/apps/ledger-live-mobile/src/families/solana/operationDetails.tsx +++ b/apps/ledger-live-mobile/src/families/solana/operationDetails.tsx @@ -79,10 +79,12 @@ const DelegateExtraFields = ({ account, voteAddress, amount }: DelegateExtraFiel title={t("operationDetails.extra.delegatedTo")} value={nameOrAddress} onPress={() => openAddressUrl(account.currency, voteAddress)} + testID="operationDetails-delegatedTo" />
); diff --git a/apps/ledger-live-mobile/src/screens/OperationDetails/Content.tsx b/apps/ledger-live-mobile/src/screens/OperationDetails/Content.tsx index d605a44db92c..2a977857f492 100644 --- a/apps/ledger-live-mobile/src/screens/OperationDetails/Content.tsx +++ b/apps/ledger-live-mobile/src/screens/OperationDetails/Content.tsx @@ -408,7 +408,11 @@ export default function Content({ {uniqueSenders.length > 0 && ( - } /> + } + /> )} diff --git a/libs/ui/packages/native/src/components/cta/QuickAction/QuickActionButton/index.tsx b/libs/ui/packages/native/src/components/cta/QuickAction/QuickActionButton/index.tsx index f241359c0a33..1afc1e94e14d 100644 --- a/libs/ui/packages/native/src/components/cta/QuickAction/QuickActionButton/index.tsx +++ b/libs/ui/packages/native/src/components/cta/QuickAction/QuickActionButton/index.tsx @@ -49,6 +49,10 @@ const QuickActionButton = ({ testID, ...otherProps }: QuickActionButtonProps): React.ReactElement => { + const text = // Extract the text to use it as a testID + React.isValidElement(children) && children.props?.i18nKey + ? children.props.i18nKey.split(".").pop() // Extract the last part of the key + : children?.toString().toLowerCase(); return (