diff --git a/apps/ledger-live-desktop/tests/enum/Account.ts b/apps/ledger-live-desktop/tests/enum/Account.ts index 46f169e47570..82ff5145f127 100644 --- a/apps/ledger-live-desktop/tests/enum/Account.ts +++ b/apps/ledger-live-desktop/tests/enum/Account.ts @@ -1,10 +1,12 @@ import { Currency } from "./Currency"; +import { AccountType } from "tests/enum/AccountType"; export class Account { constructor( public readonly currency: Currency, public readonly accountName: string, public readonly address: string, + public readonly accountType?: AccountType, ) {} static readonly BTC_NATIVE_SEGWIT_1 = new Account( @@ -257,12 +259,14 @@ export class Account { Currency.ETH_USDT, "Ethereum 1", "0xB9051f83AC6e147924377BBEebd1Aa7aB43a67F6", + AccountType.ERC20, ); static readonly ETH_USDT_2 = new Account( Currency.ETH_USDT, "Ethereum 2", "0x43047a5023D55a8658Fcb1c1Cea468311AdAA3Ad", + AccountType.ERC20, ); static readonly ETH_LIDO = new Account( @@ -299,6 +303,7 @@ export class Account { Currency.TRX_USDT, "Tron 1", "TDUKFB9wj3P5f2iNvkRuaDDeWVkTdUVhs1", + AccountType.TRC20, ); static readonly TRX_BTT = new Account( diff --git a/apps/ledger-live-desktop/tests/enum/AccountType.ts b/apps/ledger-live-desktop/tests/enum/AccountType.ts new file mode 100644 index 000000000000..14945edf2b00 --- /dev/null +++ b/apps/ledger-live-desktop/tests/enum/AccountType.ts @@ -0,0 +1,4 @@ +export enum AccountType { + ERC20 = "erc20", + TRC20 = "trc20", +} diff --git a/apps/ledger-live-desktop/tests/enum/Swap.ts b/apps/ledger-live-desktop/tests/enum/Swap.ts index fbe0cc2a28c4..0e1f0dc278a1 100644 --- a/apps/ledger-live-desktop/tests/enum/Swap.ts +++ b/apps/ledger-live-desktop/tests/enum/Swap.ts @@ -1,5 +1,10 @@ -export enum Providers { - CHANGELLY = "changelly", +export class Provider { + constructor( + public readonly name: string, + public readonly uiName: string, + ) {} + static readonly CHANGELLY = new Provider("changelly", "Changelly"); + static readonly ONEINCH = new Provider("oneinch", "1inch"); } export enum Rates { diff --git a/apps/ledger-live-desktop/tests/models/Swap.ts b/apps/ledger-live-desktop/tests/models/Swap.ts index 71d01098ef2d..e0702c1eed5d 100644 --- a/apps/ledger-live-desktop/tests/models/Swap.ts +++ b/apps/ledger-live-desktop/tests/models/Swap.ts @@ -1,18 +1,22 @@ import { Transaction } from "tests/models/Transaction"; import { Fee } from "tests/enum/Fee"; import { Account } from "../enum/Account"; +import { Provider } from "../enum/Swap"; export class Swap extends Transaction { + provider: Provider; + constructor( accountToDebit: Account, accountToCredit: Account, amount: string, speed: Fee, - provider?: string, + provider: Provider, public amountToReceive?: string, public feesAmount?: string, ) { super(accountToDebit, accountToCredit, amount, speed); + this.provider = provider; } public setAmountToReceive(value: string) { diff --git a/apps/ledger-live-desktop/tests/page/speculos.page.ts b/apps/ledger-live-desktop/tests/page/speculos.page.ts index 1e4a1993e239..8201c80fc207 100644 --- a/apps/ledger-live-desktop/tests/page/speculos.page.ts +++ b/apps/ledger-live-desktop/tests/page/speculos.page.ts @@ -73,17 +73,41 @@ export class SpeculosPage extends AppPage { verifyAmount(`${swap.accountToDebit.currency.ticker} ${swap.amount}`, sendAmountScreen), ).toBeTruthy(); const getAmountScreen = await pressRightUntil(sendPattern[1]); - expect( - verifyAmount( - `${swap.accountToCredit.currency.ticker} ${extractNumberFromString(swap.amountToReceive)}`, - getAmountScreen, - ), - ).toBeTruthy(); - const feesAmountScreen = await pressRightUntil(sendPattern[2]); - expect( - verifySwapFeesAmount(extractNumberFromString(swap.feesAmount), feesAmountScreen), - ).toBeTruthy(); + this.verifySwapGetAmountScreen(swap, getAmountScreen); + this.verifySwapFeesAmountScreen(swap, await pressRightUntil(sendPattern[2])); await pressRightUntil(DeviceLabels.REJECT); await pressBoth(); } + + verifySwapGetAmountScreen(swap: Swap, getAmountScreen: string[]) { + if (swap.accountToCredit.currency.name === "Solana") { + expect( + verifyAmount( + `${extractNumberFromString(swap.amountToReceive)} ${swap.accountToCredit.currency.ticker}`, + getAmountScreen, + ), + ).toBeTruthy(); + } else { + expect( + verifyAmount( + `${swap.accountToCredit.currency.ticker} ${extractNumberFromString(swap.amountToReceive)}`, + getAmountScreen, + ), + ).toBeTruthy(); + } + } + + verifySwapFeesAmountScreen(swap: Swap, feesAmountScreen: string[]) { + let speculosFeesAmount = ""; + if (swap.feesAmount) { + //max number of chars on the screen + speculosFeesAmount = + extractNumberFromString(swap.feesAmount).length < 19 + ? extractNumberFromString(swap.feesAmount) + : extractNumberFromString(swap.feesAmount).substring(0, 18); + } + expect( + verifySwapFeesAmount(swap.accountToDebit.currency.name, speculosFeesAmount, feesAmountScreen), + ).toBeTruthy(); + } } diff --git a/apps/ledger-live-desktop/tests/page/swap.page.ts b/apps/ledger-live-desktop/tests/page/swap.page.ts index c4f483d878b7..a2ed1dfd702e 100644 --- a/apps/ledger-live-desktop/tests/page/swap.page.ts +++ b/apps/ledger-live-desktop/tests/page/swap.page.ts @@ -3,6 +3,7 @@ import { waitFor } from "../utils/waitFor"; import { step } from "tests/misc/reporters/step"; import { ElectronApplication, expect } from "@playwright/test"; import { capitalizeFirstLetter } from "tests/utils/textParserUtils"; +import { Account } from "tests/enum/Account"; export class SwapPage extends AppPage { private currencyByName = (accountName: string) => this.page.getByText(accountName); // TODO: this is rubbish. Changed this @@ -115,10 +116,7 @@ export class SwapPage extends AppPage { } @step("Select exchange quote $0 with rate $1") - async selectExchangeQuote( - providerName: "changelly" | "cic" | "oneinch" | "paraswap", - exchangeType: "fixed" | "float", - ) { + async selectExchangeQuote(providerName: string, exchangeType: "fixed" | "float") { await this.quoteContainer(providerName, exchangeType).click(); } @@ -150,13 +148,35 @@ export class SwapPage extends AppPage { return this.detailsSwapId.innerText(); } - @step("Select account to swap from: $0") - async selectAccountToSwapFrom(accountToSwapFrom: string) { + getAccountName(account: Account) { + //erc20 accounts names are stored in account currency property + return account.accountType ? account.currency.name : account.accountName; + } + + async waitForAccountsBalance(accountAddress: string, timeout = 10000) { + const regex = new RegExp(`/blockchain/v\\d+/.+/${accountAddress}$`); + const fetchTxByAddressResponse = this.page.waitForResponse(response => { + return regex.test(response.url()) && response.status() === 200; + }); + const timeoutPromise = new Promise(resolve => setTimeout(resolve, timeout, null)); + return Promise.race([fetchTxByAddressResponse, timeoutPromise]); + } + + @step("Select account to swap from") + async selectAccountToSwapFrom(accountToSwapFrom: Account) { + await this.waitForAccountsBalance(accountToSwapFrom.address); await this.originCurrencyDropdown.click(); - await this.dropdownOptions.locator(this.optionWithText(accountToSwapFrom)).click(); + const accName = this.getAccountName(accountToSwapFrom); + if (accountToSwapFrom.accountType) { + await this.dropdownOptions + .locator(this.optionWithTextAndFollowingText(accountToSwapFrom.accountName, accName)) + .first() + .click(); + } else { + await this.dropdownOptions.locator(this.optionWithText(accName)).first().click(); + } const selectedAccountFrom = this.originCurrencyDropdown.locator(this.dropdownSelectedValue); - await expect(selectedAccountFrom).toHaveText(accountToSwapFrom); - await this.waitForPageLoadState(); + await expect(selectedAccountFrom).toHaveText(accName); } @step("Fill in amount: $0") diff --git a/apps/ledger-live-desktop/tests/specs/speculos/swap.spec.ts b/apps/ledger-live-desktop/tests/specs/speculos/swap.spec.ts index 50d2c49e9adb..67fc57e6a5bf 100644 --- a/apps/ledger-live-desktop/tests/specs/speculos/swap.spec.ts +++ b/apps/ledger-live-desktop/tests/specs/speculos/swap.spec.ts @@ -4,39 +4,94 @@ import { AppInfos } from "tests/enum/AppInfos"; import { setExchangeDependencies } from "@ledgerhq/live-common/e2e/speculos"; import { Fee } from "tests/enum/Fee"; import { Swap } from "tests/models/Swap"; -import { Providers, Rates } from "tests/enum/Swap"; +import { Provider, Rates } from "tests/enum/Swap"; const swaps = [ - /*{ - swap: new Swap(Account.ETH_1, Account.BTC_NATIVE_SEGWIT_1, "0.015", Fee.MEDIUM), - },*/ { - swap: new Swap(Account.BTC_NATIVE_SEGWIT_1, Account.ETH_1, "0.0006", Fee.MEDIUM), + swap: new Swap( + Account.ETH_1, + Account.BTC_NATIVE_SEGWIT_1, + "0.02", + Fee.MEDIUM, + Provider.CHANGELLY, + ), + }, + { + swap: new Swap( + Account.BTC_NATIVE_SEGWIT_1, + Account.ETH_1, + "0.00067", + Fee.MEDIUM, + Provider.CHANGELLY, + ), + }, + { + swap: new Swap(Account.ETH_USDT_1, Account.ETH_1, "35", Fee.MEDIUM, Provider.CHANGELLY), + }, + { + swap: new Swap(Account.ETH_1, Account.SOL_1, "0.018", Fee.MEDIUM, Provider.CHANGELLY), + }, + { + swap: new Swap(Account.ETH_1, Account.ETH_USDT_1, "0.02", Fee.MEDIUM, Provider.CHANGELLY), + }, + { + swap: new Swap( + Account.BTC_NATIVE_SEGWIT_1, + Account.SOL_1, + "0.0006", + Fee.MEDIUM, + Provider.CHANGELLY, + ), + }, + { + swap: new Swap( + Account.BTC_NATIVE_SEGWIT_1, + Account.ETH_USDT_1, + "0.0006", + Fee.MEDIUM, + Provider.CHANGELLY, + ), + }, + { + swap: new Swap( + Account.ETH_USDT_1, + Account.BTC_NATIVE_SEGWIT_1, + "35", + Fee.MEDIUM, + Provider.CHANGELLY, + ), + }, + { + swap: new Swap(Account.ETH_USDT_1, Account.SOL_1, "40", Fee.MEDIUM, Provider.CHANGELLY), }, ]; const app: AppInfos = AppInfos.EXCHANGE; for (const { swap } of swaps) { - test.beforeAll(async () => { - process.env.SWAP_DISABLE_APPS_INSTALL = "true"; - process.env.SWAP_API_BASE = "https://swap-stg.ledger.com/v5"; - }); + test.describe("Swap", () => { + test.beforeAll(async () => { + process.env.SWAP_DISABLE_APPS_INSTALL = "true"; + process.env.SWAP_API_BASE = "https://swap-stg.ledger.com/v5"; + }); - test.afterAll(async () => { - delete process.env.SWAP_DISABLE_APPS_INSTALL; - delete process.env.SWAP_API_BASE; - }); + test.beforeEach(async () => { + setExchangeDependencies( + accPair.map(appName => ({ + name: appName, + })), + ); + }); + + test.afterAll(async () => { + delete process.env.SWAP_DISABLE_APPS_INSTALL; + delete process.env.SWAP_API_BASE; + }); - test.describe.serial("Swap", () => { const accPair: string[] = [swap.accountToDebit, swap.accountToCredit].map(acc => - acc.currency.name.replace(/ /g, "_"), - ); - setExchangeDependencies( - accPair.map(appName => ({ - name: appName, - })), + acc.currency.speculosApp.name.replace(/ /g, "_"), ); + test.use({ userdata: "speculos-tests-app", speculosApp: app, @@ -46,15 +101,13 @@ for (const { swap } of swaps) { app, electronApp, }) => { - const provider = Providers.CHANGELLY; const rate = Rates.FLOAT; await app.layout.goToSwap(); - await app.swap.selectAccountToSwapFrom(swap.accountToDebit.accountName); + await app.swap.selectAccountToSwapFrom(swap.accountToDebit); await app.swap.selectCurrencyToSwapTo(swap.accountToCredit.currency.name); - await app.swap.fillInOriginAmount(swap.amount); - await app.swap.selectExchangeQuote(provider, rate); - await app.swap.clickExchangeButton(electronApp, provider); + await app.swap.selectExchangeQuote(swap.provider.name, rate); + await app.swap.clickExchangeButton(electronApp, swap.provider.uiName); const amountTo = await app.swapDrawer.getAmountToReceive(); const fees = await app.swapDrawer.getFees(); swap.setAmountToReceive(amountTo); @@ -63,7 +116,7 @@ for (const { swap } of swaps) { await app.swapDrawer.verifyAmountSent(swap.amount, swap.accountToDebit.currency.ticker); await app.swapDrawer.verifySourceAccount(swap.accountToDebit.currency.name); await app.swapDrawer.verifyTargetCurrency(swap.accountToCredit.currency.name); - await app.swapDrawer.verifyProvider(provider); + await app.swapDrawer.verifyProvider(swap.provider.name); await app.speculos.verifyAmountsAndRejectSwap(swap); await app.swapDrawer.verifyExchangeErrorTextContent("Operation denied on device"); }); diff --git a/libs/ledger-live-common/src/e2e/speculos.ts b/libs/ledger-live-common/src/e2e/speculos.ts index 64ca13d49fb2..8aece66d0e0c 100644 --- a/libs/ledger-live-common/src/e2e/speculos.ts +++ b/libs/ledger-live-common/src/e2e/speculos.ts @@ -30,7 +30,13 @@ export type Spec = { export type Dependency = { name: string; appVersion?: string }; export function setExchangeDependencies(dependencies: Dependency[]) { - specs["Exchange"].dependencies = dependencies; + const map = new Map(); + for (const dep of dependencies) { + if (!map.has(dep.name)) { + map.set(dep.name, dep); + } + } + specs["Exchange"].dependencies = Array.from(map.values()); } type Specs = { @@ -375,8 +381,16 @@ export function verifyAmount(amount: string, text: string[]): boolean { return amountDevice.includes(amount); } -export function verifySwapFeesAmount(amount: string, text: string[]): boolean { - const amountDevice = text[3]; +export function verifySwapFeesAmount(currency: string, amount: string, text: string[]): boolean { + let amountDevice = text[1]; + switch (currency) { + case "Ethereum": + amountDevice = text[3]; + break; + case "Tether USD": + amountDevice = text[3]; + break; + } return amountDevice.includes(amount); } diff --git a/libs/speculos-transport/src/index.ts b/libs/speculos-transport/src/index.ts index d365acb56b46..c47f3eb9af0d 100644 --- a/libs/speculos-transport/src/index.ts +++ b/libs/speculos-transport/src/index.ts @@ -222,6 +222,7 @@ export async function createSpeculosDevice( "40000", ]), ]; + console.log("speculos", `${speculosID}: spawning = ${params.join(" ")}`); log("speculos", `${speculosID}: spawning = ${params.join(" ")}`);