mirror of
https://github.com/zhigang1992/xverse-web-extension.git
synced 2026-01-12 09:43:44 +08:00
* Hide rare sats warning (#827) * Hide warning * Remove unused translation and blog link * feat: add keystone account import * feat: adaptor keystone transaction transport * feat: support keystone sign message * feat: support keystone btc send * update: package.json fix: en.json * uplift: clean unused code * fix: package name * add: sendOrdinal, signPsbt, psbt confirm keystone transport * fix: import tip, sign psbt error msg * fix: e2e test account connect wallet button * fix: sign mfp check, remove keystone auto confirm, sign tip * fix: selectAccount options * fix: keystone batchPsbtSigning and isKeystone * Fixes and improvements * Revert lock file * Fix lock file * Update core and fix types * Move keystone utils to correct location * Add keystone functionality to create inscription screen * Add speed up txn keystone functionality * Fix error rename * Fix keystone speed up txn * Fix some hw wallet logci * Remove link to shop * Bump core * Fix lock file merge * Bump core and fix outdated dependency * Bump core * Fix the Keystone flow header and copy * Update the props type * Remove the double spinning button state * Remove unneeded if * Move Ledger and Keystone assets to hw folder * Update sats-connect core and remove keystone todos * Bump core * Fix core bump issue * Fix keystone ordinals send * Remove keystone page that wasn't in figma * Fix strange behavior when deleting accounts * Fix merge issue * Add the intemediate Connect Hardware Wallet screen * Fix the e2e tests * Update copy for the Connect hardware wallet button --------- Co-authored-by: Tim Man <tim@secretkeylabs.com> Co-authored-by: Den <36603049+dhriaznov@users.noreply.github.com> Co-authored-by: Jordan K <65149726+jordankzf@users.noreply.github.com> Co-authored-by: Ken Liao <ken@secretkeylabs.com> Co-authored-by: keystoneGithub <eng@keyst.one> Co-authored-by: Denys Hriaznov <hriaznov.dev@gmail.com>
1235 lines
46 KiB
TypeScript
1235 lines
46 KiB
TypeScript
import { expect, type Locator, type Page } from '@playwright/test';
|
|
import Onboarding from './onboarding';
|
|
|
|
const strongPW = Onboarding.generateSecurePasswordCrypto();
|
|
|
|
export default class Wallet {
|
|
readonly balance: Locator;
|
|
|
|
readonly allUpperButtons: Locator;
|
|
|
|
readonly labelAccountName: Locator;
|
|
|
|
readonly buttonGenerateAccount: Locator;
|
|
|
|
readonly buttonConnectHardwareWallet: Locator;
|
|
|
|
readonly inputName: Locator;
|
|
|
|
readonly buttonAccountOptions: Locator;
|
|
|
|
readonly accountBalance: Locator;
|
|
|
|
readonly buttonRenameAccount: Locator;
|
|
|
|
readonly buttonResetAccountName: Locator;
|
|
|
|
readonly labelInfoRenameAccount: Locator;
|
|
|
|
readonly errorMessageRenameAccount: Locator;
|
|
|
|
readonly manageTokenButton: Locator;
|
|
|
|
readonly buttonMenu: Locator;
|
|
|
|
readonly buttonLock: Locator;
|
|
|
|
readonly buttonConfirm: Locator;
|
|
|
|
readonly buttonDenyDataCollection: Locator;
|
|
|
|
readonly buttonNetwork: Locator;
|
|
|
|
readonly buttonSave: Locator;
|
|
|
|
readonly buttonMainnet: Locator;
|
|
|
|
readonly buttonTestnet: Locator;
|
|
|
|
readonly buttonBack: Locator;
|
|
|
|
readonly inputStacksURL: Locator;
|
|
|
|
readonly inputBTCURL: Locator;
|
|
|
|
readonly inputFallbackBTCURL: Locator;
|
|
|
|
readonly labelCoinTitle: Locator;
|
|
|
|
readonly checkboxToken: Locator;
|
|
|
|
readonly checkboxTokenActive: Locator;
|
|
|
|
readonly checkboxTokenInactive: Locator;
|
|
|
|
readonly buttonStacks: Locator;
|
|
|
|
readonly buttonBRC20: Locator;
|
|
|
|
readonly buttonRunes: Locator;
|
|
|
|
readonly headingTokens: Locator;
|
|
|
|
readonly divTokenRow: Locator;
|
|
|
|
readonly labelTokenSubtitle: Locator;
|
|
|
|
readonly labelCoinBalanceCurrency: Locator;
|
|
|
|
readonly navigationDashboard: Locator;
|
|
|
|
readonly navigationNFT: Locator;
|
|
|
|
readonly navigationStacking: Locator;
|
|
|
|
readonly navigationExplore: Locator;
|
|
|
|
readonly navigationSettings: Locator;
|
|
|
|
readonly divAppSlide: Locator;
|
|
|
|
readonly divAppCard: Locator;
|
|
|
|
readonly divAppTitle: Locator;
|
|
|
|
readonly carouselApp: Locator;
|
|
|
|
readonly buttonDownArrow: Locator;
|
|
|
|
readonly inputSwapAmount: Locator;
|
|
|
|
readonly buttonSelectCoin: Locator;
|
|
|
|
readonly buttonContinue: Locator;
|
|
|
|
readonly buttonDetails: Locator;
|
|
|
|
readonly nameToken: Locator;
|
|
|
|
readonly buttonInsufficientBalance: Locator;
|
|
|
|
readonly imageToken: Locator;
|
|
|
|
readonly swapTokenBalance: Locator;
|
|
|
|
readonly textUSD: Locator;
|
|
|
|
readonly buttonStartStacking: Locator;
|
|
|
|
readonly headingStacking: Locator;
|
|
|
|
readonly containerStackingInfo: Locator;
|
|
|
|
readonly infoTextStacking: Locator;
|
|
|
|
readonly buttonUpdatePassword: Locator;
|
|
|
|
readonly errorMessage: Locator;
|
|
|
|
readonly headerNewPassword: Locator;
|
|
|
|
readonly infoUpdatePassword: Locator;
|
|
|
|
readonly buttonCurrency: Locator;
|
|
|
|
readonly buttonShowSeedphrase: Locator;
|
|
|
|
readonly textCurrency: Locator;
|
|
|
|
readonly iconFlag: Locator;
|
|
|
|
readonly selectCurrency: Locator;
|
|
|
|
readonly totalItem: Locator;
|
|
|
|
readonly tabsCollectiblesItems: Locator;
|
|
|
|
readonly containersCollectibleItem: Locator;
|
|
|
|
readonly containerRareSats: Locator;
|
|
|
|
readonly containerInscription: Locator;
|
|
|
|
readonly nameInscription: Locator;
|
|
|
|
readonly amountInscription: Locator;
|
|
|
|
readonly buttonNext: Locator;
|
|
|
|
readonly inputMemo: Locator;
|
|
|
|
readonly inputRecipientAddress: Locator;
|
|
|
|
readonly inputSendAmount: Locator;
|
|
|
|
readonly errorMessageAddressInvalid: Locator;
|
|
|
|
readonly errorInsufficientBalance: Locator;
|
|
|
|
readonly errorMessageAddressRequired: Locator;
|
|
|
|
readonly containerFeeRate: Locator;
|
|
|
|
readonly inputBTCAddress: Locator;
|
|
|
|
readonly coinBalance: Locator;
|
|
|
|
readonly transactionHistoryAmount: Locator;
|
|
|
|
readonly containerTransactionHistory: Locator;
|
|
|
|
readonly errorMessageSendSelf: Locator;
|
|
|
|
readonly infoMessageSendSelf: Locator;
|
|
|
|
readonly inputBTCAmount: Locator;
|
|
|
|
readonly buttonExpand: Locator;
|
|
|
|
readonly confirmCurrencyAmount: Locator;
|
|
|
|
readonly confirmTotalAmount: Locator;
|
|
|
|
readonly confirmAmount: Locator;
|
|
|
|
readonly sendAddress: Locator;
|
|
|
|
readonly receiveAddress: Locator;
|
|
|
|
readonly confirmBalance: Locator;
|
|
|
|
readonly buttonCancel: Locator;
|
|
|
|
readonly labelCoinBalanceCrypto: Locator;
|
|
|
|
readonly labelBalanceAmountSelector: Locator;
|
|
|
|
readonly buttonClose: Locator;
|
|
|
|
readonly sendTransactionID: Locator;
|
|
|
|
readonly errorInsufficientFunds: Locator;
|
|
|
|
readonly noFundsBTCMessage: Locator;
|
|
|
|
readonly coinSecondaryButton: Locator;
|
|
|
|
readonly coinSecondaryContainer: Locator;
|
|
|
|
readonly coinContractAddress: Locator;
|
|
|
|
readonly textCoinTitle: Locator;
|
|
|
|
readonly sendSTXValue: Locator;
|
|
|
|
readonly buttonList: Locator;
|
|
|
|
readonly tabAvailable: Locator;
|
|
|
|
readonly tabListed: Locator;
|
|
|
|
readonly tabNotListed: Locator;
|
|
|
|
readonly buttonSetPrice: Locator;
|
|
|
|
readonly runeSKIBIDI: Locator;
|
|
|
|
readonly runeItem: Locator;
|
|
|
|
readonly runeItemCheckbox: Locator;
|
|
|
|
readonly buttonFloorPrice: Locator;
|
|
|
|
readonly button5Price: Locator;
|
|
|
|
readonly button10Price: Locator;
|
|
|
|
readonly button20Price: Locator;
|
|
|
|
readonly buttonCustomPrice: Locator;
|
|
|
|
readonly buttonApply: Locator;
|
|
|
|
readonly inputListingPrice: Locator;
|
|
|
|
readonly runeContainer: Locator;
|
|
|
|
readonly runeTitle: Locator;
|
|
|
|
readonly runePrice: Locator;
|
|
|
|
readonly buttonEnable: Locator;
|
|
|
|
readonly buttonSend: Locator;
|
|
|
|
readonly labelSatsValue: Locator;
|
|
|
|
readonly labelOwnedBy: Locator;
|
|
|
|
readonly labelBundle: Locator;
|
|
|
|
readonly buttonSupportRarity: Locator;
|
|
|
|
readonly itemCollection: Locator;
|
|
|
|
readonly backToGallery: Locator;
|
|
|
|
readonly buttonShare: Locator;
|
|
|
|
readonly buttonOpenOrdinalViewer: Locator;
|
|
|
|
readonly numberInscription: Locator;
|
|
|
|
readonly numberOrdinal: Locator;
|
|
|
|
readonly containersCollectibleItemCollection: Locator;
|
|
|
|
readonly containersCollectibleItemSingle: Locator;
|
|
|
|
readonly nameInscriptionCollection: Locator;
|
|
|
|
readonly nameInscriptionSingle: Locator;
|
|
|
|
readonly sendAmount: Locator;
|
|
|
|
readonly sendCurrencyAmount: Locator;
|
|
|
|
readonly listedRune: Locator;
|
|
|
|
readonly signingAddress: Locator;
|
|
|
|
readonly buttonSign: Locator;
|
|
|
|
readonly buttonReload: Locator;
|
|
|
|
readonly listedRunePrice: Locator;
|
|
|
|
readonly buttonGetQuotes: Locator;
|
|
|
|
readonly buttonSwap: Locator;
|
|
|
|
readonly inputField: Locator;
|
|
|
|
readonly buttonEditFee: Locator;
|
|
|
|
readonly feeAmount: Locator;
|
|
|
|
readonly transactionHistoryInfo: Locator;
|
|
|
|
readonly buttonReceive: Locator;
|
|
|
|
readonly sendRuneAmount: Locator;
|
|
|
|
readonly buttonInsufficientFunds: Locator;
|
|
|
|
readonly nameSwapPlace: Locator;
|
|
|
|
readonly quoteAmount: Locator;
|
|
|
|
readonly infoMessage: Locator;
|
|
|
|
readonly buttonSwapPlace: Locator;
|
|
|
|
readonly buttonSlippage: Locator;
|
|
|
|
readonly buttonSwapToken: Locator;
|
|
|
|
readonly minReceivedAmount: Locator;
|
|
|
|
readonly nameRune: Locator;
|
|
|
|
readonly buttonSelectFee: Locator;
|
|
|
|
readonly labelTotalFee: Locator;
|
|
|
|
readonly itemUTXO: Locator;
|
|
|
|
readonly titleUTXO: Locator;
|
|
|
|
readonly buttonQRAddress: Locator;
|
|
|
|
readonly labelAddress: Locator;
|
|
|
|
readonly containerQRCode: Locator;
|
|
|
|
readonly labelFeePriority: Locator;
|
|
|
|
readonly divAddress: Locator;
|
|
|
|
readonly buttonPreferences: Locator;
|
|
|
|
readonly buttonSecurity: Locator;
|
|
|
|
readonly buttonAdvanced: Locator;
|
|
|
|
readonly errorInsufficientBRC20Balance: Locator;
|
|
|
|
readonly BRC20FeeAmount: Locator;
|
|
|
|
readonly buttonTransactionSend: Locator;
|
|
|
|
constructor(readonly page: Page) {
|
|
this.page = page;
|
|
this.navigationDashboard = page.getByTestId('nav-dashboard');
|
|
this.navigationNFT = page.getByTestId('nav-nft');
|
|
this.navigationStacking = page.getByTestId('nav-stacking');
|
|
this.navigationExplore = page.getByTestId('nav-explore');
|
|
this.navigationSettings = page.getByTestId('nav-settings');
|
|
// this.balance = page.getByTestId('total-balance-value');
|
|
this.balance = page.getByLabel(/^Total balance/);
|
|
this.textCurrency = page.getByTestId('currency-text');
|
|
this.allUpperButtons = page.getByTestId('transaction-buttons-row').getByRole('button');
|
|
this.buttonTransactionSend = this.allUpperButtons.nth(0);
|
|
this.manageTokenButton = page.getByRole('button', { name: 'Manage token list' });
|
|
this.buttonMenu = page.getByRole('button', { name: 'Open Header Options' });
|
|
this.buttonLock = page.getByRole('button', { name: 'Lock' });
|
|
this.buttonConfirm = page.getByRole('button', { name: 'Confirm' });
|
|
this.buttonDenyDataCollection = page.getByRole('button', { name: 'Deny' });
|
|
this.labelBalanceAmountSelector = page.getByTestId('balance-label');
|
|
this.buttonClose = page.getByRole('button', { name: 'Close' });
|
|
this.buttonEditFee = page.getByTestId('fee-button');
|
|
this.feeAmount = page.getByTestId('fee-amount');
|
|
this.BRC20FeeAmount = page.getByTestId('brc20-fee');
|
|
this.buttonSelectFee = page.getByTestId('fee-select-button');
|
|
this.labelTotalFee = page.getByTestId('total-fee');
|
|
this.labelFeePriority = page.getByTestId('fee-priority');
|
|
|
|
// Account
|
|
this.labelAccountName = page.getByLabel('Account Name');
|
|
this.buttonAccountOptions = page.getByLabel('Open Account Options');
|
|
this.accountBalance = page.getByTestId('account-balance');
|
|
this.buttonRenameAccount = page.getByRole('button', { name: 'Rename account' });
|
|
this.buttonResetAccountName = page.getByRole('button', { name: 'Reset name' });
|
|
this.labelInfoRenameAccount = page
|
|
.locator('form div')
|
|
.filter({ hasText: 'name can only include alphabetical and numerical' });
|
|
this.buttonGenerateAccount = page.getByRole('button', { name: 'Generate account' });
|
|
this.buttonConnectHardwareWallet = page.getByRole('button', {
|
|
name: 'Add hardware wallet account',
|
|
});
|
|
this.inputName = page.locator('input[type="text"]');
|
|
this.errorMessageRenameAccount = page
|
|
.locator('p')
|
|
.filter({ hasText: 'contain alphabetic and numeric' });
|
|
|
|
// Settings network
|
|
this.buttonNetwork = page.getByRole('button', { name: 'Network' });
|
|
this.buttonSave = page.getByRole('button', { name: 'Save' });
|
|
this.buttonMainnet = page.getByRole('button', { name: 'Mainnet' });
|
|
this.buttonTestnet = page.getByRole('button', { name: 'Testnet' });
|
|
this.buttonPreferences = page.getByRole('button', { name: 'Preferences' });
|
|
this.buttonSecurity = page.getByRole('button', { name: 'Security' });
|
|
this.buttonAdvanced = page.getByRole('button', { name: 'Advanced' });
|
|
this.buttonBack = page.getByTestId('back-button');
|
|
this.buttonNext = page.getByRole('button', { name: 'Next' });
|
|
this.inputStacksURL = page.getByTestId('Stacks URL');
|
|
this.inputBTCURL = page.getByTestId('BTC URL');
|
|
this.inputFallbackBTCURL = page.getByTestId('Fallback BTC URL');
|
|
this.buttonUpdatePassword = page.getByRole('button', { name: 'Update Password' });
|
|
this.errorMessage = page.getByText(/incorrect password/i);
|
|
this.headerNewPassword = page.getByRole('heading', { name: 'Enter your new password' });
|
|
this.infoUpdatePassword = page.getByText(/password successfully updated/i);
|
|
this.buttonCurrency = page.getByRole('button', { name: 'Fiat Currency' });
|
|
this.buttonShowSeedphrase = page.getByRole('button', { name: 'Show Seedphrase' });
|
|
this.selectCurrency = page.getByTestId('currency-button');
|
|
this.iconFlag = page.locator('img[alt="flag"]');
|
|
|
|
// Token
|
|
this.labelCoinTitle = page.getByLabel('Coin Title');
|
|
this.checkboxToken = page.locator('label[role="checkbox"]');
|
|
this.checkboxTokenActive = page.locator('label[role="checkbox"][aria-checked="true"]');
|
|
this.checkboxTokenInactive = page.locator('label[role="checkbox"][aria-checked="false"]');
|
|
this.buttonStacks = page.getByRole('button', { name: 'STACKS' });
|
|
this.buttonBRC20 = page.getByRole('button', { name: 'BRC-20' });
|
|
this.buttonRunes = page.getByRole('button', { name: 'RUNES' });
|
|
this.headingTokens = page.getByRole('heading', { name: 'Manage tokens' });
|
|
|
|
this.divTokenRow = page.getByLabel('Token Row');
|
|
this.labelTokenSubtitle = page.getByLabel('Token SubTitle');
|
|
this.labelCoinBalanceCurrency = page.getByLabel('Currency Balance Container').locator('span');
|
|
this.labelCoinBalanceCrypto = page.getByLabel('CoinBalance Container').locator('p');
|
|
|
|
// Coin details
|
|
this.coinBalance = page.getByTestId('coin-balance');
|
|
this.containerTransactionHistory = page.getByTestId('transaction-container');
|
|
this.transactionHistoryAmount = page.getByTestId('transaction-amount');
|
|
this.transactionHistoryInfo = page.getByTestId('transaction-info');
|
|
this.coinSecondaryButton = page.getByTestId('coin-secondary-button');
|
|
this.coinSecondaryContainer = page.getByTestId('coin-secondary-container');
|
|
this.coinContractAddress = page.getByTestId('coin-contract-address');
|
|
this.textCoinTitle = page.getByTestId('coin-title-text');
|
|
|
|
// Collectibles
|
|
this.totalItem = page.getByTestId('total-items');
|
|
this.tabsCollectiblesItems = page.getByTestId('tab-list').locator('button');
|
|
this.containerRareSats = page.getByTestId('rareSats-container');
|
|
this.nameInscription = page.getByTestId('inscription-name');
|
|
this.containersCollectibleItem = page.getByTestId('collection-container');
|
|
this.amountInscription = page.getByTestId('inscription-amount');
|
|
this.containersCollectibleItemCollection = this.containersCollectibleItem.filter({
|
|
has: this.amountInscription.filter({
|
|
hasText: /\d+\s+item(s)?/i,
|
|
}),
|
|
});
|
|
this.containersCollectibleItemSingle = this.containersCollectibleItem.filter({
|
|
has: this.amountInscription.filter({
|
|
hasText: /^$/,
|
|
}),
|
|
});
|
|
this.nameInscriptionCollection =
|
|
this.containersCollectibleItemCollection.getByTestId('inscription-name');
|
|
this.nameInscriptionSingle =
|
|
this.containersCollectibleItemSingle.getByTestId('inscription-name');
|
|
this.buttonEnable = page.getByRole('button', { name: 'Enable' });
|
|
this.containerInscription = page.getByTestId('inscription-container');
|
|
|
|
this.backToGallery = page.getByTestId('back-to-gallery');
|
|
this.itemCollection = page.getByTestId('collection-item');
|
|
this.buttonSend = page.locator('button').filter({ hasText: 'Send' });
|
|
this.buttonShare = page.locator('button').filter({ hasText: 'Share' });
|
|
this.buttonReceive = page.getByRole('button', { name: /^Receive/i });
|
|
|
|
this.buttonOpenOrdinalViewer = page.getByRole('button', { name: 'Open in Ordinal Viewer' });
|
|
this.labelBundle = page.locator('h1').filter({ hasText: 'Bundle' });
|
|
this.labelSatsValue = page.locator('h1').filter({ hasText: 'Sats value' });
|
|
this.labelOwnedBy = page.locator('h1').filter({ hasText: 'Owned by' });
|
|
this.buttonSupportRarity = page.getByRole('button', { name: 'See supported rarity scale' });
|
|
this.numberInscription = page.getByTestId('inscription-number');
|
|
this.numberOrdinal = page.getByTestId('ordinal-number');
|
|
|
|
// Explore
|
|
this.carouselApp = page.getByTestId('app-carousel');
|
|
this.divAppSlide = page.getByTestId('app-slide');
|
|
this.divAppCard = page.getByTestId('app-card');
|
|
this.divAppTitle = page.getByTestId('app-title');
|
|
|
|
// Receive
|
|
this.buttonQRAddress = page.getByTestId('qr-button');
|
|
this.labelAddress = page.getByTestId('address-label');
|
|
this.divAddress = page.getByTestId('address-div');
|
|
this.containerQRCode = page.getByTestId('qr-container');
|
|
|
|
// Swap
|
|
this.imageToken = page.getByTestId('token-image');
|
|
this.buttonSelectCoin = page.getByTestId('select-coin-button');
|
|
this.inputSwapAmount = page.getByTestId('swap-amount');
|
|
this.nameToken = page.getByTestId('token-name');
|
|
this.buttonDownArrow = page.getByTestId('down-arrow-button');
|
|
this.buttonContinue = page.getByRole('button', { name: 'Continue' });
|
|
this.buttonGetQuotes = page.getByRole('button', { name: 'Get quotes' });
|
|
this.buttonSwap = page.getByRole('button', { name: 'Swap', exact: true });
|
|
this.nameSwapPlace = page.getByTestId('place-name');
|
|
this.quoteAmount = page.getByTestId('quote-label');
|
|
this.infoMessage = page.getByTestId('info-message');
|
|
this.buttonSwapPlace = page.getByTestId('swap-place-button');
|
|
this.buttonSwapToken = page.getByTestId('swap-token-button');
|
|
this.buttonSlippage = page.getByTestId('slippage-button');
|
|
this.minReceivedAmount = page.getByTestId('min-received-amount');
|
|
this.nameRune = page.getByTestId('rune-name');
|
|
this.itemUTXO = page.getByTestId('utxo-item');
|
|
this.titleUTXO = page.getByTestId('utxo-title');
|
|
|
|
this.buttonDetails = page.getByRole('button', { name: 'Details' });
|
|
this.buttonInsufficientBalance = page.getByRole('button', { name: 'Insufficient balance' });
|
|
this.buttonInsufficientFunds = page.getByRole('button', { name: 'Insufficient funds' });
|
|
|
|
this.swapTokenBalance = page.getByTestId('swap-token-balance');
|
|
this.textUSD = page.getByTestId('usd-text');
|
|
this.noFundsBTCMessage = page.getByTestId('no-funds-message');
|
|
|
|
// Send
|
|
this.inputSendAmount = page.getByTestId('send-input');
|
|
this.inputRecipientAddress = page.getByTestId('recipient-address');
|
|
this.inputMemo = page.getByTestId('memo-input');
|
|
this.errorMessageAddressInvalid = page
|
|
.locator('p')
|
|
.filter({ hasText: 'Recipient address invalid' });
|
|
this.errorMessageAddressRequired = page
|
|
.locator('p')
|
|
.filter({ hasText: 'Recipient address is required' });
|
|
this.infoMessageSendSelf = page
|
|
.locator('p')
|
|
.filter({ hasText: 'You are transferring to yourself' });
|
|
this.errorMessageSendSelf = page.locator('p').filter({ hasText: 'Cannot send to self' });
|
|
this.errorInsufficientBalance = page.locator('p').filter({ hasText: 'Insufficient balance' });
|
|
this.errorInsufficientBRC20Balance = page.getByText('Insufficient BRC-20 balance');
|
|
this.errorInsufficientFunds = page.locator('p').filter({ hasText: 'Insufficient funds' });
|
|
this.containerFeeRate = page.getByTestId('feerate-container');
|
|
this.inputBTCAddress = page.locator('input[type="text"]');
|
|
this.inputBTCAmount = page.getByTestId('btc-amount');
|
|
this.buttonExpand = page.getByRole('button', { name: 'Inputs & Outputs' });
|
|
this.confirmTotalAmount = page.getByTestId('confirm-total-amount');
|
|
this.confirmCurrencyAmount = page.getByTestId('confirm-currency-amount');
|
|
this.confirmAmount = page.getByTestId('confirm-amount');
|
|
this.sendAddress = page.getByTestId('address-send');
|
|
this.receiveAddress = page.getByTestId('address-receive');
|
|
this.confirmBalance = page.getByTestId('confirm-balance');
|
|
this.buttonCancel = page.getByRole('button', { name: 'Cancel' });
|
|
this.buttonSign = page.getByRole('button', { name: 'Sign' });
|
|
this.sendTransactionID = page.getByTestId('transaction-id');
|
|
this.sendSTXValue = page.getByTestId('send-value');
|
|
this.inputField = page.locator('input[type="text"]');
|
|
this.sendRuneAmount = page.getByTestId('send-rune-amount');
|
|
|
|
// List
|
|
this.buttonList = page.getByRole('button', { name: 'List List' });
|
|
this.tabAvailable = page.getByTestId('available-tab');
|
|
this.tabListed = page.getByRole('button', { name: 'LISTED', exact: true });
|
|
this.tabNotListed = page.getByRole('button', { name: 'NOT LISTED' });
|
|
this.listedRune = page.getByTestId('listed-rune-container');
|
|
|
|
this.buttonSetPrice = page.getByRole('button', { name: 'Set price' });
|
|
// this is the test rune to be used for listing
|
|
this.runeSKIBIDI = page.getByTestId('SKIBIDI•OHIO•RIZZ');
|
|
this.runeItem = page.getByTestId('rune-item');
|
|
this.runeItemCheckbox = page.locator('#list-rune');
|
|
this.buttonFloorPrice = page.getByRole('button', { name: 'Floor', exact: true });
|
|
this.button5Price = page.getByRole('button', { name: '+5%', exact: true });
|
|
this.button10Price = page.getByRole('button', { name: '+10%', exact: true });
|
|
this.button20Price = page.getByRole('button', { name: '+20%', exact: true });
|
|
this.buttonCustomPrice = page.getByRole('button', { name: 'Custom', exact: true });
|
|
this.buttonApply = page.getByRole('button', { name: 'Apply', exact: true });
|
|
this.inputListingPrice = page.locator('input[type="number"]');
|
|
this.runeContainer = page.getByTestId('rune-container');
|
|
this.runeTitle = page.getByTestId('rune-title');
|
|
this.runePrice = page.getByTestId('rune-price').locator('p').filter({ hasText: 'sats' });
|
|
this.sendAmount = page.getByTestId('send-amount');
|
|
this.sendCurrencyAmount = page.getByTestId('send-currency-amount');
|
|
this.signingAddress = page.getByTestId('signing-address');
|
|
this.buttonReload = page.getByTestId('reload-button');
|
|
this.listedRunePrice = page.getByTestId('listed-price');
|
|
|
|
// Stacking
|
|
this.buttonStartStacking = page.getByRole('button', { name: 'Start stacking' });
|
|
this.headingStacking = page.getByRole('heading', { name: 'Stack STX, earn BTC' });
|
|
this.containerStackingInfo = page.getByTestId('stacking-info');
|
|
this.infoTextStacking = page.locator('h1').filter({ hasText: 'STX with other stackers' });
|
|
}
|
|
|
|
// Helper function to restore the wallet, switch to testnet if parameter is true and navigate to dashboard
|
|
async setupTest(extensionId, wallet, testnet) {
|
|
const onboardingPage = new Onboarding(this.page);
|
|
await onboardingPage.restoreWallet(strongPW, wallet);
|
|
await this.page.goto(`chrome-extension://${extensionId}/popup.html`);
|
|
await this.checkVisualsStartpage();
|
|
if (testnet) {
|
|
await this.navigationSettings.click();
|
|
await this.switchToTestnetNetwork();
|
|
await this.navigationDashboard.click();
|
|
await this.checkVisualsStartpage();
|
|
}
|
|
}
|
|
|
|
async checkVisualsStartpage() {
|
|
// to-do fix the element itself, after the native-segwit update it resolves to 2 elements
|
|
// data-testid="total-balance-value"
|
|
await expect(this.balance.first()).toBeVisible();
|
|
await expect(this.manageTokenButton).toBeVisible();
|
|
|
|
// Deny data collection --> modal window is not always appearing so when it does we deny the data collection
|
|
if (await this.buttonDenyDataCollection.isVisible()) {
|
|
await this.buttonDenyDataCollection.click();
|
|
}
|
|
|
|
await expect(this.labelAccountName).toBeVisible();
|
|
await expect(this.buttonMenu).toBeVisible();
|
|
// expect(await this.labelTokenSubtitle.count()).toBeGreaterThanOrEqual(2);
|
|
|
|
await expect(this.navigationDashboard).toBeVisible();
|
|
await expect(this.navigationNFT).toBeVisible();
|
|
await expect(this.navigationStacking).toBeVisible();
|
|
await expect(this.navigationExplore).toBeVisible();
|
|
await expect(this.navigationSettings).toBeVisible();
|
|
expect(await this.divTokenRow.count()).toBeGreaterThan(1);
|
|
}
|
|
|
|
async checkVisualsSendSTXPage3() {
|
|
expect(this.page.url()).toContain('confirm-stx-tx');
|
|
await expect(this.buttonConfirm).toBeVisible();
|
|
await expect(this.buttonCancel).toBeVisible();
|
|
await expect(this.receiveAddress).toBeVisible();
|
|
await expect(this.confirmAmount).toBeVisible();
|
|
await expect(this.buttonEditFee).toBeVisible();
|
|
await expect(this.feeAmount).toBeVisible();
|
|
await expect(this.buttonBack).toBeVisible();
|
|
}
|
|
|
|
/**
|
|
* Checks the visibility and state of UI elements state on first page in Send Flow
|
|
*
|
|
* @param {string} url - The expected URL to validate the correct page navigation.
|
|
* @param {boolean} moreInputFields - (default: false).
|
|
*/
|
|
async checkVisualsSendPage1(url: string, moreInputFields: boolean = false) {
|
|
expect(this.page.url()).toContain(url);
|
|
await expect(this.buttonNext).toBeVisible();
|
|
await expect(this.buttonNext).toBeDisabled();
|
|
|
|
if (moreInputFields) {
|
|
// Recipient Address for STX or amount for BRC20
|
|
await expect(this.inputField.first()).toBeVisible();
|
|
// Memo for STX or recipient for BRC20
|
|
await expect(this.inputField.last()).toBeVisible();
|
|
} else {
|
|
await expect(this.receiveAddress).toBeVisible();
|
|
}
|
|
|
|
await expect(this.imageToken).toBeVisible();
|
|
// await expect(this.buttonBack).toBeVisible();
|
|
}
|
|
|
|
/**
|
|
* Validates UI element visibility and state on second page in Send Flow
|
|
*
|
|
* @param {string} url - URL to verify page navigation; ensures the test is on the correct page.
|
|
* @param {boolean} isSTX - Indicates if the page is STX-specific; adjusts element checks accordingly (default: false).
|
|
*/
|
|
async checkVisualsSendPage2(url: string, isSTX: boolean = false) {
|
|
expect(this.page.url()).toContain(url);
|
|
await expect(this.buttonNext).toBeVisible();
|
|
await expect(this.buttonNext).toBeDisabled();
|
|
|
|
// Conditional check based on the type of token
|
|
if (isSTX) {
|
|
// STX-specific fields
|
|
await expect(this.inputField.first()).toBeVisible();
|
|
} else {
|
|
// General send amount field
|
|
await expect(this.inputSendAmount).toBeVisible();
|
|
}
|
|
|
|
await expect(this.labelBalanceAmountSelector).toBeVisible();
|
|
await expect(this.buttonEditFee).toBeVisible();
|
|
await expect(this.feeAmount).toBeVisible();
|
|
await expect(this.imageToken).toBeVisible();
|
|
// await expect(this.buttonBack).toBeVisible();
|
|
}
|
|
|
|
/**
|
|
* Checks the visuals and elements on the send transaction review page.
|
|
*
|
|
* @param {string} url - The expected partial URL of the review page.
|
|
* @param {boolean} editableFees - Optional. Indicates whether the fees can be edited on the Review page
|
|
* @param {string} sendAddress - Optional. The expected last 4 characters of the sender's address
|
|
* @param {string} recipientAddress - Optional. The expected last 4 characters of the receiver's address
|
|
* @param {boolean} totalAmountShown - Optional. Indicates whether the total amount is shown. Default is true.
|
|
* @param {boolean} tokenImageShown - Optional. Indicates whether the token image is shown. Default is true.
|
|
* @param {string} ordinalNumber - Optional. The expected ordinal number to be displayed for single Inscriptions
|
|
*/
|
|
async checkVisualsSendTransactionReview(
|
|
url: string,
|
|
editableFees?: boolean,
|
|
sendAddress?: string,
|
|
recipientAddress?: string,
|
|
totalAmountShown: boolean = true,
|
|
tokenImageShown: boolean = true,
|
|
ordinalNumber?: string,
|
|
) {
|
|
expect(this.page.url()).toContain(url);
|
|
await expect(this.buttonExpand).toBeVisible();
|
|
await expect(this.buttonCancel).toBeEnabled();
|
|
await expect(this.buttonConfirm).toBeEnabled();
|
|
await expect(this.feeAmount).toBeVisible();
|
|
|
|
// Not all TX Screens show a total amount
|
|
if (totalAmountShown) {
|
|
await expect(this.confirmTotalAmount.first()).toBeVisible();
|
|
await expect(this.confirmCurrencyAmount.first()).toBeVisible();
|
|
}
|
|
|
|
if (tokenImageShown) {
|
|
await expect(this.imageToken.first()).toBeVisible();
|
|
}
|
|
|
|
if (editableFees) {
|
|
await expect(this.buttonEditFee).toBeVisible();
|
|
}
|
|
|
|
await this.buttonExpand.click();
|
|
await expect(this.confirmAmount.first()).toBeVisible();
|
|
await expect(this.confirmBalance.first()).toBeVisible();
|
|
|
|
// Execute these checks only if sendAddress is provided
|
|
if (sendAddress) {
|
|
await expect(this.sendAddress.first()).toBeVisible();
|
|
expect(await this.sendAddress.first().innerText()).toContain(sendAddress.slice(-4));
|
|
}
|
|
|
|
// Execute these checks only if recipientAddress is provided
|
|
if (recipientAddress) {
|
|
await expect(this.receiveAddress.first()).toBeVisible();
|
|
expect(await this.receiveAddress.first().innerText()).toContain(recipientAddress.slice(-4));
|
|
}
|
|
// Collection Inscriptions don't have the ordinal number displayed in the Review
|
|
// Check if the right ordinal number is shown
|
|
if (ordinalNumber) {
|
|
const reviewNumberOrdinal = await this.numberInscription.first().innerText();
|
|
expect(ordinalNumber).toMatch(reviewNumberOrdinal);
|
|
}
|
|
}
|
|
|
|
// Check Visuals of Rune Dashboard (without List button), return balance amount
|
|
async checkVisualsRunesDashboard(runeName: string) {
|
|
await expect(this.imageToken.first()).toBeVisible();
|
|
await expect(this.textCoinTitle).toBeVisible();
|
|
await expect(this.textCoinTitle).toContainText(runeName);
|
|
await expect(this.coinBalance).toBeVisible();
|
|
await expect(this.buttonReceive).toBeVisible();
|
|
await expect(this.buttonSend).toBeVisible();
|
|
const originalBalanceText = await this.coinBalance.innerText();
|
|
const numericOriginalValue = parseFloat(originalBalanceText.replace(/[^\d.-]/g, ''));
|
|
return numericOriginalValue;
|
|
}
|
|
|
|
async checkVisualsListRunesPage() {
|
|
await expect(this.tabNotListed).toBeVisible();
|
|
await expect(this.tabListed).toBeVisible();
|
|
await expect(this.runeItem.first()).toBeVisible();
|
|
expect(await this.runeItem.count()).toBeGreaterThanOrEqual(1);
|
|
}
|
|
|
|
async checkVisualsSwapPage() {
|
|
expect(this.page.url()).toContain('swap');
|
|
await expect(this.buttonDownArrow.first()).toBeVisible();
|
|
await expect(this.buttonGetQuotes.first()).toBeVisible();
|
|
await expect(this.buttonGetQuotes.first()).toBeDisabled();
|
|
await expect(this.inputSwapAmount.first()).toBeVisible();
|
|
await expect(this.swapTokenBalance).toContainText('--');
|
|
await expect(this.buttonBack).toBeVisible();
|
|
await expect(this.nameToken.first()).toContainText('Select asset');
|
|
await expect(this.nameToken).toHaveCount(2);
|
|
await expect(this.buttonDownArrow).toHaveCount(2);
|
|
await expect(this.buttonGetQuotes).toBeVisible();
|
|
await expect(this.textUSD).toBeVisible();
|
|
await expect(this.buttonSwapToken).toBeVisible();
|
|
}
|
|
|
|
// Helper function to fill in swap amount and returns usd value as number
|
|
async fillSwapAmount(amount) {
|
|
// .Fill() did not work with the field so we need to use this method
|
|
await this.inputSwapAmount.pressSequentially(amount.toString());
|
|
await expect(this.buttonGetQuotes).toBeEnabled();
|
|
|
|
const usdAmount = await this.textUSD.innerText();
|
|
const numericUSDValue = parseFloat(usdAmount.replace(/[^0-9.]/g, ''));
|
|
expect(numericUSDValue).toBeGreaterThan(0);
|
|
|
|
return numericUSDValue;
|
|
}
|
|
|
|
// had to disable this rule as my first assertion was always changed to a wrong assertion
|
|
/* eslint-disable playwright/prefer-web-first-assertions */
|
|
async checkVisualsQuotePage(
|
|
tokenName: string,
|
|
slippage: boolean,
|
|
numericQuoteValue: number,
|
|
numericUSDValue: number,
|
|
) {
|
|
await expect(this.buttonSwap).toBeVisible();
|
|
await expect(this.buttonEditFee).toBeVisible();
|
|
if (slippage) {
|
|
await expect(this.buttonSlippage).toBeVisible();
|
|
}
|
|
// Only 2 token should be visible
|
|
expect(await this.buttonSwapPlace.count()).toBe(2);
|
|
// await expect(await this.imageToken.count()).toBe(2);
|
|
|
|
// Check Rune token name
|
|
await expect(this.infoMessage.last()).toContainText(tokenName);
|
|
|
|
// Check if USD amount from quote page is the same as from th swap start flow page
|
|
const usdAmountQuote = await this.textUSD.first().innerText();
|
|
const numericUSDQuote = parseFloat(usdAmountQuote.replace(/[^0-9.]/g, ''));
|
|
expect(numericUSDQuote).toEqual(numericUSDValue);
|
|
}
|
|
// check visuals of List on ME page
|
|
|
|
async checkVisualsListOnMEPage() {
|
|
await expect(this.buttonFloorPrice).toBeVisible();
|
|
await expect(this.button5Price).toBeVisible();
|
|
await expect(this.button10Price).toBeVisible();
|
|
await expect(this.button20Price).toBeVisible();
|
|
await expect(this.buttonCustomPrice).toBeVisible();
|
|
await expect(this.buttonContinue).toBeVisible();
|
|
await expect(this.buttonContinue).toBeDisabled();
|
|
await expect(this.runeContainer.first()).toBeVisible();
|
|
}
|
|
|
|
async invalidAddressCheck(addressField) {
|
|
await addressField.fill(`Test Address 123`);
|
|
await this.buttonNext.click();
|
|
await expect(this.errorMessageAddressInvalid).toBeVisible();
|
|
await expect(this.buttonNext).toBeDisabled();
|
|
}
|
|
|
|
// had to disable this rule as my first assertion was always changed to a wrong assertion
|
|
/* eslint-disable playwright/prefer-web-first-assertions */
|
|
async switchToHighFees(feePriorityShown: boolean = true) {
|
|
// Save the current fee amount for comparison
|
|
const originalFee = await this.feeAmount.innerText();
|
|
const numericOriginalFee = parseFloat(originalFee.replace(/[^0-9.]/g, ''));
|
|
expect(numericOriginalFee).toBeGreaterThan(0);
|
|
let feePriority = 'Medium';
|
|
if (feePriorityShown) {
|
|
feePriority = await this.labelFeePriority.innerText();
|
|
}
|
|
|
|
// Click on edit Fee button
|
|
await this.buttonEditFee.click();
|
|
await expect(this.buttonSelectFee.first()).toBeVisible();
|
|
await expect(this.labelTotalFee.first()).toBeVisible();
|
|
|
|
// Compare fee to previous saved fee
|
|
const fee = await this.buttonSelectFee
|
|
.filter({ hasText: feePriority })
|
|
.locator(this.labelTotalFee)
|
|
.innerText();
|
|
const numericFee = parseFloat(fee.replace(/[^0-9.]/g, ''));
|
|
expect(numericFee).toBe(numericOriginalFee);
|
|
|
|
// Save high fee rate for comparison
|
|
const highFee = await this.labelTotalFee.first().innerText();
|
|
const numericHighFee = parseFloat(highFee.replace(/[^0-9.]/g, ''));
|
|
|
|
// Switch to high fee
|
|
await this.buttonSelectFee.first().click();
|
|
|
|
const newFee = await this.feeAmount.innerText();
|
|
const numericNewFee = parseFloat(newFee.replace(/[^0-9.]/g, ''));
|
|
expect(numericNewFee).toBe(numericHighFee);
|
|
}
|
|
|
|
async navigateToCollectibles() {
|
|
await this.navigationNFT.click();
|
|
expect(this.page.url()).toContain('nft-dashboard');
|
|
// If 'enable' rare sats pop up is appearing
|
|
if (await this.buttonEnable.isVisible()) {
|
|
await this.buttonEnable.click();
|
|
}
|
|
// Check visuals on opening Collectibles page
|
|
await expect(this.tabsCollectiblesItems.first()).toBeVisible();
|
|
await expect(this.totalItem).toBeVisible();
|
|
}
|
|
|
|
// had to disable this rule as my first assertion was always changed to a wrong assertion
|
|
async checkAmountsSendingSTX(amountSTXSend, STXTest, sendFee) {
|
|
expect(await this.receiveAddress.first().innerText()).toContain(STXTest.slice(-4));
|
|
|
|
// Sending amount without Fee
|
|
const sendAmount = await this.confirmAmount.first().innerText();
|
|
const numericValueSendAmount = parseFloat(sendAmount.replace(/[^0-9.]/g, ''));
|
|
|
|
expect(numericValueSendAmount).toEqual(amountSTXSend);
|
|
|
|
// Fees
|
|
const fee = await this.feeAmount.innerText();
|
|
const numericValueFee = parseFloat(fee.replace(/[^0-9.]/g, ''));
|
|
expect(numericValueFee).toEqual(sendFee);
|
|
}
|
|
|
|
async checkAmountsSendingBTC(selfBTCTest, BTCTest, amountBTCSend) {
|
|
// Sending amount without Fee
|
|
const amountText = await this.confirmAmount.first().innerText();
|
|
const numericValueAmountText = parseFloat(amountText.replace(/[^0-9.]/g, ''));
|
|
expect(numericValueAmountText).toEqual(amountBTCSend);
|
|
|
|
// Address check sending and receiving
|
|
expect(await this.sendAddress.innerText()).toContain(selfBTCTest.slice(-4));
|
|
expect(await this.receiveAddress.first().innerText()).toContain(BTCTest.slice(-4));
|
|
|
|
const confirmAmountAfter = await this.confirmAmount.last().innerText();
|
|
const originalFee = await this.feeAmount.innerText();
|
|
const confirmBalance = await this.confirmBalance.innerText();
|
|
// Extract amounts for balance, sending amount and amount afterwards
|
|
const num1 = parseFloat(confirmAmountAfter.replace(/[^0-9.]/g, ''));
|
|
// We need to convert the sats value to BTC for this calculation
|
|
const feeSatsAmount = parseFloat(originalFee.replace(/[^0-9.]/g, ''));
|
|
const num2 = feeSatsAmount / 100000000;
|
|
const num3 = parseFloat(confirmBalance.replace(/[^0-9.]/g, ''));
|
|
|
|
// Balance - fees - sending amount
|
|
const roundedResult = Number((num3 - num2 - amountBTCSend).toFixed(9));
|
|
// Check if Balance value after the transaction is the same as the calculated value
|
|
expect(num1).toEqual(roundedResult);
|
|
}
|
|
|
|
async confirmSendTransaction(transactionIDShown: boolean = true) {
|
|
await expect(this.buttonConfirm).toBeEnabled();
|
|
await this.buttonConfirm.click();
|
|
await expect(this.buttonClose).toBeVisible({ timeout: 30000 });
|
|
if (transactionIDShown) {
|
|
await expect(this.sendTransactionID).toBeVisible();
|
|
}
|
|
await this.buttonClose.click();
|
|
}
|
|
|
|
async getAddress(whichAddress: string): Promise<string> {
|
|
// click on 'Receive' button
|
|
await this.allUpperButtons.nth(1).click();
|
|
|
|
// Locate the QR button to the address
|
|
const button = this.divAddress.filter({ hasText: whichAddress }).locator(this.buttonQRAddress);
|
|
|
|
// Need to click on the QR Code button to get the full Address
|
|
await button.click();
|
|
await expect(this.containerQRCode).toBeVisible();
|
|
|
|
const address = await this.labelAddress.innerText();
|
|
|
|
await this.buttonBack.click();
|
|
|
|
return address;
|
|
}
|
|
|
|
async getTokenBalance(tokenname: string) {
|
|
try {
|
|
const locator = this.page
|
|
.locator('button')
|
|
.filter({ hasText: new RegExp(`${tokenname}.*\\d`) });
|
|
|
|
console.log(`Looking for balance for ${tokenname}`);
|
|
const balanceText = await locator.innerText();
|
|
console.log('Found balance text:', balanceText);
|
|
|
|
// Extract just the numeric portion (matches any number with decimal points)
|
|
const matches = balanceText.match(/\d+\.?\d*/);
|
|
if (!matches) {
|
|
throw new Error(`Could not extract balance from text: ${balanceText}`);
|
|
}
|
|
|
|
const numericValue = parseFloat(matches[0]);
|
|
console.log('Parsed numeric value:', numericValue);
|
|
return numericValue;
|
|
} catch (error) {
|
|
console.error(`Error getting balance for ${tokenname}:`, error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async clickOnSpecificToken(tokenname: string) {
|
|
const specificToken = this.page.getByRole('button').getByLabel(`Token Title: ${tokenname}`);
|
|
// filter({
|
|
// has: this.labelTokenSubtitle.getByText(tokenname, { exact: true }),
|
|
|
|
await specificToken.last().click();
|
|
}
|
|
|
|
async clickOnSpecificInscription(inscriptionName: string) {
|
|
const specificToken = this.containersCollectibleItem
|
|
.filter({
|
|
has: this.nameInscription.getByText(inscriptionName, { exact: true }),
|
|
})
|
|
.getByTestId('inscription-container');
|
|
await specificToken.last().click();
|
|
}
|
|
|
|
// This function tries to click on a specific rune, if the rune is not enabled it will enable the test rune and then click on it
|
|
async checkAndClickOnSpecificRune(tokenname: string) {
|
|
// Check if test rune is enabled and if not enabled the test rune
|
|
try {
|
|
// click on the test rune
|
|
await this.clickOnSpecificToken(tokenname);
|
|
} catch (error) {
|
|
// if the rune was not clickable we need to enable the test rune
|
|
// Execute alternative commands if an error occurs
|
|
console.log('Test Rune is not active and need to be enabled');
|
|
// Insert your fallback logic here
|
|
// check if the test rune is enabled
|
|
await this.manageTokenButton.click();
|
|
await this.buttonRunes.click();
|
|
await expect(this.divTokenRow.first()).toBeVisible();
|
|
await expect(this.runeSKIBIDI).toBeVisible({ timeout: 30000 });
|
|
|
|
// if clause for enabled check
|
|
const count = await this.checkboxTokenActive.count();
|
|
// If no runes are enabled enable the Test rune
|
|
if (count !== 1) {
|
|
console.log(
|
|
'No active token checkbox found or there are multiple, taking alternative action.',
|
|
);
|
|
// Activate Test rune
|
|
await this.runeSKIBIDI.locator('label[role="checkbox"]').click();
|
|
} else {
|
|
console.log('One active token checkbox is present.');
|
|
}
|
|
// Switch back to dashboard and click on the rune
|
|
await this.buttonBack.click();
|
|
await this.clickOnSpecificToken(tokenname);
|
|
}
|
|
}
|
|
|
|
async checkNetworkSettingVisuals() {
|
|
await expect(this.buttonSave).toBeVisible();
|
|
await expect(this.buttonBack).toBeVisible();
|
|
await expect(this.buttonMainnet).toBeVisible();
|
|
await expect(this.buttonTestnet).toBeVisible();
|
|
await expect(this.inputStacksURL).toBeVisible();
|
|
await expect(this.inputBTCURL).toBeVisible();
|
|
await expect(this.inputFallbackBTCURL).toBeVisible();
|
|
}
|
|
|
|
async checkTestnetUrls(shouldContainTestnet: boolean) {
|
|
const inputsURL = [this.inputStacksURL, this.inputBTCURL, this.inputFallbackBTCURL];
|
|
const checks = inputsURL.map(async (input) => {
|
|
const inputValue = await input.inputValue();
|
|
const message = `URL does not contain 'testnet' in ${input}`;
|
|
if (shouldContainTestnet) {
|
|
return expect(inputValue, message).toContain('testnet');
|
|
}
|
|
return expect(inputValue, message).not.toContain('testnet');
|
|
});
|
|
await Promise.all(checks);
|
|
}
|
|
|
|
async switchToTestnetNetwork() {
|
|
await expect(this.buttonNetwork).toBeVisible();
|
|
await expect(this.buttonNetwork).toHaveText('NetworkMainnet');
|
|
await this.buttonNetwork.click();
|
|
await this.checkNetworkSettingVisuals();
|
|
await expect(this.buttonMainnet.locator('img')).toHaveAttribute('alt', 'tick');
|
|
await expect(this.buttonTestnet.locator('img[alt="tick"]')).toHaveCount(0);
|
|
|
|
await this.buttonTestnet.click();
|
|
await expect(this.buttonTestnet.locator('img')).toHaveAttribute('alt', 'tick');
|
|
await expect(this.buttonMainnet.locator('img[alt="tick"]')).toHaveCount(0);
|
|
|
|
await this.inputStacksURL.fill('https://api.nakamoto.testnet.hiro.so');
|
|
// To speed up some checks we use our own servers
|
|
// await this.inputBTCURL.fill('https://btc-testnet.xverse.app');
|
|
|
|
await this.checkTestnetUrls(true);
|
|
|
|
// TODO think of a better way to do this
|
|
// Wait for the network to be switched so that API doesn't fail because of the rate limiting
|
|
await this.page.waitForTimeout(15000);
|
|
|
|
await this.buttonSave.click();
|
|
await expect(this.buttonNetwork).toBeVisible({ timeout: 30000 });
|
|
await expect(this.buttonNetwork).toHaveText('NetworkTestnet');
|
|
}
|
|
|
|
async switchToMainnetNetwork() {
|
|
await expect(this.buttonNetwork).toBeVisible();
|
|
await expect(this.buttonNetwork).toHaveText('NetworkTestnet');
|
|
await this.buttonNetwork.click();
|
|
await this.checkNetworkSettingVisuals();
|
|
await expect(this.buttonTestnet.locator('img')).toHaveAttribute('alt', 'tick');
|
|
await expect(this.buttonMainnet.locator('img[alt="tick"]')).toHaveCount(0);
|
|
|
|
await this.buttonMainnet.click();
|
|
await expect(this.buttonMainnet.locator('img')).toHaveAttribute('alt', 'tick');
|
|
await expect(this.buttonTestnet.locator('img[alt="tick"]')).toHaveCount(0);
|
|
|
|
await this.checkTestnetUrls(false);
|
|
|
|
// TODO think of a better way to do this
|
|
// Wait for the network to be switched so that API doesn't fail because of the rate limiting
|
|
await this.page.waitForTimeout(15000);
|
|
|
|
await this.buttonSave.click();
|
|
await expect(this.buttonNetwork).toBeVisible({ timeout: 30000 });
|
|
await expect(this.buttonNetwork).toHaveText('NetworkMainnet');
|
|
}
|
|
|
|
async getBalanceOfAllAccounts() {
|
|
const count = await this.accountBalance.count();
|
|
let totalBalance = 0;
|
|
if (count > 1) {
|
|
for (let i = 0; i < count; i++) {
|
|
const balanceText = await this.accountBalance.nth(i).innerText();
|
|
const numericValue = parseFloat(balanceText.replace(/[^\d.-]/g, ''));
|
|
totalBalance += numericValue;
|
|
}
|
|
} else {
|
|
const balanceText = await this.accountBalance.innerText();
|
|
totalBalance = parseFloat(balanceText.replace(/[^\d.-]/g, ''));
|
|
}
|
|
return totalBalance;
|
|
}
|
|
|
|
async getBalanceOfAllTokens() {
|
|
const count = await this.labelCoinBalanceCurrency.count();
|
|
let totalBalance = 0;
|
|
if (count > 1) {
|
|
for (let i = 0; i < count; i++) {
|
|
const balanceText = await this.labelCoinBalanceCurrency.nth(i).innerText();
|
|
const numericValue = parseFloat(balanceText.replace(/[^\d.-]/g, ''));
|
|
totalBalance += numericValue;
|
|
}
|
|
} else {
|
|
const balanceText = await this.labelCoinBalanceCurrency.innerText();
|
|
totalBalance = parseFloat(balanceText.replace(/[^\d.-]/g, ''));
|
|
}
|
|
return totalBalance;
|
|
}
|
|
|
|
async selectLastToken(tokenType: 'BRC20' | 'STACKS'): Promise<string> {
|
|
await this.manageTokenButton.click();
|
|
expect(this.page.url()).toContain('manage-tokens');
|
|
|
|
// Click on the specific token type button if BRC20 is selected
|
|
if (tokenType === 'BRC20') {
|
|
await this.buttonBRC20.click();
|
|
} else if (tokenType === 'STACKS') {
|
|
await this.buttonStacks.click();
|
|
}
|
|
|
|
const chosenToken = this.divTokenRow.last();
|
|
const tokenName = (await chosenToken.getAttribute('data-testid')) || 'default-value';
|
|
await this.buttonBack.click();
|
|
return tokenName;
|
|
}
|
|
|
|
// The function toggleRandomToken takes a boolean parameter enable to determine the action:
|
|
// true indicates enabling a token (using inactive tokens).
|
|
// false indicates disabling a token (using active tokens).
|
|
async toggleRandomToken(enable: boolean): Promise<string> {
|
|
const tokenStateLocator = enable ? this.checkboxTokenInactive : this.checkboxTokenActive;
|
|
await expect(tokenStateLocator.first()).toBeVisible();
|
|
const numberOfTokens = await tokenStateLocator.count();
|
|
|
|
// Generate a random index within the range of available tokens
|
|
const chosenIndex = Math.floor(Math.random() * numberOfTokens);
|
|
|
|
// Access the nth token (adjusting for zero-based indexing)
|
|
const chosenToken = this.divTokenRow.filter({ has: tokenStateLocator }).nth(chosenIndex);
|
|
const tokenName = (await chosenToken.getAttribute('data-testid')) || 'default-value';
|
|
|
|
// Click the switch handle to toggle the token's state
|
|
await chosenToken.locator('label[role="checkbox"]').click();
|
|
|
|
return tokenName;
|
|
}
|
|
|
|
// The function toggleAllTokens takes a boolean parameter enable.
|
|
// true indicates enabling token (using inactive tokens).
|
|
// false indicates disabling token (using active tokens).
|
|
async toggleAllTokens(enable: boolean) {
|
|
// Determine which tokens to interact with based on the 'enable' parameter
|
|
const tokenSelector = enable ? this.checkboxTokenInactive : this.checkboxTokenActive;
|
|
const actionTokens = this.divTokenRow.filter({ has: tokenSelector });
|
|
const count = await actionTokens.count();
|
|
|
|
for (let i = 0; i < count; i++) {
|
|
// Since clicking the switch will change its state, always interact with the first one
|
|
await actionTokens.first().locator('label[role="checkbox"]').click();
|
|
}
|
|
}
|
|
|
|
// Helper function to wait for a field to get greater than 0. Some fields are slowly to load for the E2E so we need to ensure that their is a value loaded before continue
|
|
async waitForTextAboveZero(selector, timeout = 30000) {
|
|
const startTime = Date.now();
|
|
while (true) {
|
|
if (Date.now() - startTime > timeout) {
|
|
throw new Error('Timeout waiting for text to be above 0');
|
|
}
|
|
const text = await selector.innerText();
|
|
const numericValue = parseFloat(text.replace(/[^0-9.]/g, ''));
|
|
if (numericValue > 0) {
|
|
return; // Exit the function when the condition is met
|
|
}
|
|
await this.page.waitForTimeout(1000); // Check every second
|
|
}
|
|
}
|
|
}
|