From 51b2889bd1ac13b43f4bbd658c7ded86bd697af4 Mon Sep 17 00:00:00 2001 From: Michael <30682308+mike10ca@users.noreply.github.com> Date: Thu, 12 Oct 2023 19:49:49 +0200 Subject: [PATCH] Tests: Add Assets tests (#2624) * Add Assets tests * Update test description --- cypress/e2e/pages/balances.pages.js | 197 +++++++++++++++++++++++- cypress/e2e/pages/main.page.js | 34 +++++ cypress/e2e/smoke/assets.cy.js | 222 ++++++++++++++++++++++++++++ cypress/e2e/smoke/balances.cy.js | 2 +- cypress/support/constants.js | 2 + 5 files changed, 449 insertions(+), 8 deletions(-) create mode 100644 cypress/e2e/smoke/assets.cy.js diff --git a/cypress/e2e/pages/balances.pages.js b/cypress/e2e/pages/balances.pages.js index 4c640272b2..84a95a5a2d 100644 --- a/cypress/e2e/pages/balances.pages.js +++ b/cypress/e2e/pages/balances.pages.js @@ -1,4 +1,7 @@ -const etherscanLink = 'a[aria-label="View on goerli.etherscan.io"]' +import * as main from '../pages/main.page' + +let etherscanLink = 'a[aria-label="View on goerli.etherscan.io"]' +let etherscanLinkSepolia = 'a[aria-label="View on sepolia.etherscan.io"]' export const balanceSingleRow = '[aria-labelledby="tableTitle"] > tbody tr' const currencyDropdown = '[id="currency"]' const currencyDropdownList = 'ul[role="listbox"]' @@ -8,8 +11,20 @@ const hiddeTokensBtn = '[data-testid="toggle-hidden-assets"]' const hiddenTokenCheckbox = 'input[type="checkbox"]' const paginationPageList = 'ul[role="listbox"]' const currencyDropDown = 'div[id="currency"]' -const hiddenTokenSaveBtn = 'Save' +export const tokenListTable = 'table[aria-labelledby="tableTitle"]' +const tokenListDropdown = 'div[id="tokenlist-select"]' + +const hiddenTokenSaveBtn = 'span[data-track="assets: Save hide dialog"]' +const hiddenTokenCancelBtn = 'span[data-track="assets: Cancel hide dialog"]' +const hiddenTokenDeselectAllBtn = 'span[data-track="assets: Deselect all hide dialog"]' +const hiddenTokenIcon = 'svg[data-testid="VisibilityOffOutlinedIcon"]' + const hideTokenDefaultString = 'Hide tokens' +const gotItStr = 'Got it!' +const assetNameSortBtnStr = 'Asset' +const assetBalanceSortBtnStr = 'Balance' +const sendBtnStr = 'Send' +const sendTokensStr = 'Send tokens' const pageRowsDefault = '25' const rowsPerPage10 = '10' @@ -21,12 +36,40 @@ const pageCountString1to25 = '1–25 of' const pageCountString1to10 = '1–10 of' const pageCountString10to20 = '11–20 of' +export const fiatRegex = new RegExp(`([0-9]{1,3},)*[0-9]{1,3}.[0-9]{2}`) + +export const tokenListOptions = { + allTokens: 'span[data-track="assets: Show all tokens"]', + default: 'span[data-track="assets: Show default tokens"]', +} export const currencyEUR = 'EUR' export const currencyUSD = 'USD' +export const currencyAave = 'AAVE' +export const currencyAaveAlttext = 'AAVE' +export const currentcyAaveFormat = '27 AAVE' + +export const currencyTestTokenA = 'TestTokenA' +export const currencyTestTokenAAlttext = 'TT_A' +export const currentcyTestTokenAFormat = '15 TT_A' + +export const currencyTestTokenB = 'TestTokenB' +export const currencyTestTokenBAlttext = 'TT_B' +export const currentcyTestTokenBFormat = '21 TT_B' + +export const currencyUSDC = 'USDC' +export const currencyTestUSDCAlttext = 'USDC' +export const currentcyTestUSDCFormat = '73 USDC' + +export const currencyLink = 'LINK' +export const currencyLinkAlttext = 'LINK' +export const currentcyLinkFormat = '35.94 LINK' + export const currencyDai = 'Dai' +export const currencyDaiCap = 'DAI' export const currencyDaiAlttext = 'DAI' export const currentcyDaiFormat = '120,496.61 DAI' +export const currencyDaiFormat_2 = '82 DAI' export const currencyEther = 'Wrapped Ether' export const currencyEtherAlttext = 'WETH' @@ -48,6 +91,121 @@ export const currentcyGnosisFormat = '< 0.00001 GNO' export const currencyOx = /^0x$/ export const currentcyOxFormat = '1.003 ZRX' +export function clickOnSendBtn(index) { + cy.get('button') + .contains(sendBtnStr) + .then((elements) => { + cy.wrap(elements[index]).invoke('css', 'opacity', 100).click() + }) + cy.get('div').contains(sendTokensStr).should('exist') +} + +export function showSendBtn(index) { + cy.get('button') + .contains(sendBtnStr) + .then((elements) => { + cy.wrap(elements[index]).invoke('css', 'opacity', 100).trigger('mouseover', { force: true }) + }) +} + +export function verifyTableRows(assetsLength) { + cy.get(balanceSingleRow).should('have.length', assetsLength) +} + +export function clickOnTokenNameSortBtn() { + cy.get('span').contains(assetNameSortBtnStr).click() + cy.wait(500) +} + +export function clickOnTokenBalanceSortBtn() { + cy.get('span').contains(assetBalanceSortBtnStr).click() + cy.wait(500) +} + +export function verifyTokenNamesOrder(option = 'ascending') { + const tokens = [] + + main.getTextToArray('tr p', tokens) + + cy.wrap(tokens).then((arr) => { + let sortedNames = [...arr].sort() + if (option == 'descending') sortedNames = [...arr].sort().reverse() + expect(arr).to.deep.equal(sortedNames) + }) +} + +export function verifyTokenBalanceOrder(option = 'ascending') { + const balances = [] + + main.extractDigitsToArray('tr td:nth-child(2) span', balances) + + cy.wrap(balances).then((arr) => { + let sortedBalance = [...arr].sort() + if (option == 'descending') sortedBalance = [...arr].sort().reverse() + expect(arr).to.deep.equal(sortedBalance) + }) +} + +export function deselecAlltHiddenTokenSelection() { + cy.get(hiddenTokenDeselectAllBtn).click() +} + +export function cancelSaveHiddenTokenSelection() { + cy.get(hiddenTokenCancelBtn).click() +} + +export function checkTokenCounter(value) { + cy.get(hiddenTokenIcon) + .parent() + .within(() => { + cy.get('p').should('include.text', value) + }) +} + +export function checkHiddenTokenBtnCounter(value) { + cy.get(hiddeTokensBtn).within(() => { + cy.get('p').should('include.text', value) + }) +} + +export function verifyEachRowHasCheckbox(state) { + cy.get(tokenListTable).within(() => { + cy.get('tbody').within(() => { + cy.get('tr').each(($row) => { + if (state) { + cy.wrap($row).find('td').eq(3).find(hiddenTokenCheckbox).should('exist').should(state) + return + } + cy.wrap($row).find('td').eq(3).find(hiddenTokenCheckbox).should('exist') + }) + }) + }) +} + +export function acceptSpamWarning() { + cy.get('button').contains(gotItStr).click() + verifySpamWarningNotdisplayed() +} + +export function verifySpamWarningNotdisplayed() { + cy.contains(gotItStr).should('not.exist') +} +export function verifyTokensTabIsSelected(option) { + cy.get(`a[aria-selected="${option}"]`).contains('Tokens') +} + +export function verifyTokenIsPresent(token) { + cy.get(tokenListTable).contains(token) +} + +export function selectTokenList(option) { + cy.get(tokenListDropdown) + .click() + .then(() => { + cy.get(option).click() + }) +} + export function verityTokenAltImageIsVisible(currency, alttext) { cy.contains(currency) .parents('tr') @@ -56,16 +214,35 @@ export function verityTokenAltImageIsVisible(currency, alttext) { }) } -export function verifyAssetNameHasExplorerLink(currency, columnName) { - cy.contains(currency).parents('tr').find('td').eq(columnName).find(etherscanLink).should('be.visible') +export function verifyAssetNameHasExplorerLink(currency, columnName, sepolia = false) { + if (sepolia) etherscanLink = etherscanLinkSepolia + cy.get(tokenListTable) + .contains(currency) + .parents('tr') + .find('td') + .eq(columnName) + .find(etherscanLink) + .should('be.visible') +} + +export function verifyAssetExplorerLinkNotAvailable(currency, columnName) { + cy.get(tokenListTable) + .contains(currency) + .parents('tr') + .find('td') + .eq(columnName) + .within(() => { + cy.get(etherscanLink).should('not.exist') + }) } export function verifyBalance(currency, tokenAmountColumn, alttext) { - cy.contains(currency).parents('tr').find('td').eq(tokenAmountColumn).contains(alttext) + cy.get(tokenListTable).contains(currency).parents('tr').find('td').eq(tokenAmountColumn).contains(alttext) } export function verifyTokenBalanceFormat(currency, formatString, tokenAmountColumn, fiatAmountColumn, fiatRegex) { - cy.contains(currency) + cy.get(tokenListTable) + .contains(currency) .parents('tr') .within(() => { cy.get('td').eq(tokenAmountColumn).contains(formatString) @@ -103,6 +280,12 @@ export function hideAsset(asset) { export function openHideTokenMenu() { cy.get(hiddeTokensBtn).click() + main.verifyElementsExist([hiddenTokenSaveBtn, hiddenTokenCancelBtn, hiddenTokenDeselectAllBtn, hiddenTokenIcon]) + cy.get(hiddenTokenIcon) + .parent() + .within(() => { + cy.get('p') + }) } export function clickOnTokenCheckbox(token) { @@ -110,7 +293,7 @@ export function clickOnTokenCheckbox(token) { } export function saveHiddenTokenSelection() { - cy.contains(hiddenTokenSaveBtn).click() + cy.get(hiddenTokenSaveBtn).click() } export function verifyTokenIsVisible(token) { diff --git a/cypress/e2e/pages/main.page.js b/cypress/e2e/pages/main.page.js index 66b9828d36..2ea448bf23 100644 --- a/cypress/e2e/pages/main.page.js +++ b/cypress/e2e/pages/main.page.js @@ -53,3 +53,37 @@ export function generateRandomString(length) { export function verifyElementsCount(element, count) { cy.get(element).should('have.length', count) } + +export function verifyValuesDoNotExist(element, values) { + values.forEach((value) => { + cy.get(element).should('not.contain', value) + }) +} + +export function verifyValuesExist(element, values) { + values.forEach((value) => { + cy.get(element).should('contain', value) + }) +} + +export function verifyElementsExist(elements) { + elements.forEach((element) => { + cy.get(element).should('exist') + }) +} + +export function getTextToArray(selector, textArray) { + cy.get(selector).each(($element) => { + textArray.push($element.text()) + }) +} + +export function extractDigitsToArray(selector, digitsArray) { + cy.get(selector).each(($element) => { + const text = $element.text() + const digits = text.match(/\d+\.\d+|\d+\b/g) + if (digits) { + digitsArray.push(...digits) + } + }) +} diff --git a/cypress/e2e/smoke/assets.cy.js b/cypress/e2e/smoke/assets.cy.js new file mode 100644 index 0000000000..3d075c1927 --- /dev/null +++ b/cypress/e2e/smoke/assets.cy.js @@ -0,0 +1,222 @@ +import * as constants from '../../support/constants' +import * as main from '../../e2e/pages/main.page' +import * as balances from '../pages/balances.pages' +import * as owner from '../pages/owners.pages' + +const ASSET_NAME_COLUMN = 0 +const TOKEN_AMOUNT_COLUMN = 1 +const FIAT_AMOUNT_COLUMN = 2 + +describe('Assets tests', () => { + const fiatRegex = balances.fiatRegex + + beforeEach(() => { + cy.visit(constants.BALANCE_URL + constants.SEPOLIA_TEST_SAFE_5) + cy.clearLocalStorage() + main.acceptCookies() + balances.acceptSpamWarning() + }) + + it('Verify that the token tab is selected by default and the table is visible', () => { + balances.verifyTokensTabIsSelected('true') + }) + + it('Verify that the native token is visible', () => { + balances.verifyTokenIsPresent(constants.tokenNames.sepoliaEther) + }) + + it('Verify that non-native tokens are present and have balance', () => { + balances.selectTokenList(balances.tokenListOptions.allTokens) + balances.verifyBalance(balances.currencyDaiCap, TOKEN_AMOUNT_COLUMN, balances.currencyDaiAlttext) + balances.verifyTokenBalanceFormat( + balances.currencyDaiCap, + balances.currencyDaiFormat_2, + TOKEN_AMOUNT_COLUMN, + FIAT_AMOUNT_COLUMN, + fiatRegex, + ) + + balances.verifyBalance(balances.currencyAave, TOKEN_AMOUNT_COLUMN, balances.currencyAaveAlttext) + balances.verifyTokenBalanceFormat( + balances.currencyAave, + balances.currentcyAaveFormat, + TOKEN_AMOUNT_COLUMN, + FIAT_AMOUNT_COLUMN, + fiatRegex, + ) + + balances.verifyBalance(balances.currencyLink, TOKEN_AMOUNT_COLUMN, balances.currencyLinkAlttext) + balances.verifyTokenBalanceFormat( + balances.currencyLink, + balances.currentcyLinkFormat, + TOKEN_AMOUNT_COLUMN, + FIAT_AMOUNT_COLUMN, + fiatRegex, + ) + + balances.verifyBalance(balances.currencyTestTokenA, TOKEN_AMOUNT_COLUMN, balances.currencyTestTokenAAlttext) + balances.verifyTokenBalanceFormat( + balances.currencyTestTokenA, + balances.currentcyTestTokenAFormat, + TOKEN_AMOUNT_COLUMN, + FIAT_AMOUNT_COLUMN, + fiatRegex, + ) + + balances.verifyBalance(balances.currencyTestTokenB, TOKEN_AMOUNT_COLUMN, balances.currencyTestTokenBAlttext) + balances.verifyTokenBalanceFormat( + balances.currencyTestTokenB, + balances.currentcyTestTokenBFormat, + TOKEN_AMOUNT_COLUMN, + FIAT_AMOUNT_COLUMN, + fiatRegex, + ) + + balances.verifyBalance(balances.currencyUSDC, TOKEN_AMOUNT_COLUMN, balances.currencyTestUSDCAlttext) + balances.verifyTokenBalanceFormat( + balances.currencyUSDC, + balances.currentcyTestUSDCFormat, + TOKEN_AMOUNT_COLUMN, + FIAT_AMOUNT_COLUMN, + fiatRegex, + ) + }) + + it('Verify that every token except the native token has a "go to blockexplorer link"', () => { + balances.selectTokenList(balances.tokenListOptions.allTokens) + // Specifying true for Sepolia. Will delete the flag once completely migrate to Sepolia + balances.verifyAssetNameHasExplorerLink(balances.currencyUSDC, ASSET_NAME_COLUMN, true) + balances.verifyAssetNameHasExplorerLink(balances.currencyTestTokenB, ASSET_NAME_COLUMN, true) + balances.verifyAssetNameHasExplorerLink(balances.currencyTestTokenA, ASSET_NAME_COLUMN, true) + balances.verifyAssetNameHasExplorerLink(balances.currencyLink, ASSET_NAME_COLUMN, true) + balances.verifyAssetNameHasExplorerLink(balances.currencyAave, ASSET_NAME_COLUMN, true) + balances.verifyAssetNameHasExplorerLink(balances.currencyDaiCap, ASSET_NAME_COLUMN, true) + balances.verifyAssetExplorerLinkNotAvailable(constants.tokenNames.sepoliaEther, ASSET_NAME_COLUMN) + }) + + it('Verify that the default currency and the changing effects', () => { + balances.selectTokenList(balances.tokenListOptions.allTokens) + balances.verifyFirstRowDoesNotContainCurrency(balances.currencyEUR, FIAT_AMOUNT_COLUMN) + balances.verifyFirstRowContainsCurrency(balances.currencyUSD, FIAT_AMOUNT_COLUMN) + balances.clickOnCurrencyDropdown() + balances.selectCurrency(balances.currencyEUR) + balances.verifyFirstRowDoesNotContainCurrency(balances.currencyUSD, FIAT_AMOUNT_COLUMN) + balances.verifyFirstRowContainsCurrency(balances.currencyEUR, FIAT_AMOUNT_COLUMN) + }) + + it('Verify that a tool tip is shown pointing to "Token list" dropdown', () => { + //Spam warning message is removed in beforeEach hook + cy.reload() + balances.verifySpamWarningNotdisplayed() + }) + + it('Verify that Token list dropdown down options show/hide spam tokens', () => { + let spamTokens = [ + balances.currencyAave, + balances.currencyTestTokenA, + balances.currencyTestTokenB, + balances.currencyUSDC, + balances.currencyLink, + balances.currencyDaiCap, + ] + + main.verifyValuesDoNotExist(balances.tokenListTable, spamTokens) + balances.selectTokenList(balances.tokenListOptions.allTokens) + spamTokens.push(constants.tokenNames.sepoliaEther) + main.verifyValuesExist(balances.tokenListTable, spamTokens) + }) + + it('Verify that "Hide token" button is present and opens the "Hide tokens menu"', () => { + balances.selectTokenList(balances.tokenListOptions.allTokens) + balances.openHideTokenMenu() + balances.verifyEachRowHasCheckbox() + }) + + it('Verify that checking the checkboxes increases the token selected counter', () => { + balances.selectTokenList(balances.tokenListOptions.allTokens) + balances.openHideTokenMenu() + balances.clickOnTokenCheckbox(balances.currencyLink) + balances.checkTokenCounter(1) + }) + + it('Verify that selecting tokens and saving hides them from the table', () => { + balances.selectTokenList(balances.tokenListOptions.allTokens) + balances.openHideTokenMenu() + balances.clickOnTokenCheckbox(balances.currencyLink) + balances.saveHiddenTokenSelection() + main.verifyValuesDoNotExist(balances.tokenListTable, [balances.currencyLink]) + }) + + it('Verify that Cancel closes the menu and does not change the table status', () => { + balances.selectTokenList(balances.tokenListOptions.allTokens) + balances.openHideTokenMenu() + balances.clickOnTokenCheckbox(balances.currencyLink) + balances.clickOnTokenCheckbox(balances.currencyAave) + balances.saveHiddenTokenSelection() + main.verifyValuesDoNotExist(balances.tokenListTable, [balances.currencyLink, balances.currencyAave]) + balances.openHideTokenMenu() + balances.clickOnTokenCheckbox(balances.currencyLink) + balances.clickOnTokenCheckbox(balances.currencyAave) + balances.cancelSaveHiddenTokenSelection() + main.verifyValuesDoNotExist(balances.tokenListTable, [balances.currencyLink, balances.currencyAave]) + }) + + it('Verify that Deselect All unchecks all tokens from the list', () => { + balances.selectTokenList(balances.tokenListOptions.allTokens) + balances.openHideTokenMenu() + balances.clickOnTokenCheckbox(balances.currencyLink) + balances.clickOnTokenCheckbox(balances.currencyAave) + balances.deselecAlltHiddenTokenSelection() + balances.verifyEachRowHasCheckbox(constants.checkboxStates.unchecked) + }) + + it('Verify the Hidden tokens counter works for spam tokens', () => { + balances.selectTokenList(balances.tokenListOptions.allTokens) + balances.openHideTokenMenu() + balances.clickOnTokenCheckbox(balances.currencyLink) + balances.saveHiddenTokenSelection() + balances.checkHiddenTokenBtnCounter(1) + }) + + it('Verify the Hidden tokens counter works for native tokens', () => { + balances.openHideTokenMenu() + balances.clickOnTokenCheckbox(constants.tokenNames.sepoliaEther) + balances.saveHiddenTokenSelection() + balances.checkHiddenTokenBtnCounter(1) + }) + + it('Verify you can hide tokens from the eye icon in the table rows', () => { + balances.selectTokenList(balances.tokenListOptions.allTokens) + balances.hideAsset(balances.currencyLink) + }) + + it('Verify the sorting of "Assets" and "Balance" in the table', () => { + balances.selectTokenList(balances.tokenListOptions.allTokens) + balances.verifyTableRows(7) + balances.clickOnTokenNameSortBtn() + balances.verifyTokenNamesOrder() + balances.clickOnTokenNameSortBtn() + balances.verifyTokenNamesOrder('descending') + balances.clickOnTokenBalanceSortBtn() + balances.verifyTokenBalanceOrder() + balances.clickOnTokenBalanceSortBtn() + balances.verifyTokenBalanceOrder('descending') + }) + + it('Verify that clicking the button with an owner opens the Send funds form ', () => { + balances.selectTokenList(balances.tokenListOptions.allTokens) + balances.clickOnSendBtn(0) + }) + + it('Verify that the Send button shows when hovering a row', () => { + owner.clickOnWalletExpandMoreIcon() + owner.clickOnDisconnectBtn() + balances.selectTokenList(balances.tokenListOptions.allTokens) + balances.showSendBtn(0) + owner.verifyTooltiptext(owner.disconnectedUserErrorMsg) + cy.visit(constants.BALANCE_URL + constants.SEPOLIA_TEST_SAFE_4) + balances.selectTokenList(balances.tokenListOptions.allTokens) + balances.showSendBtn(0) + owner.verifyTooltiptext(owner.disconnectedUserErrorMsg) + }) +}) diff --git a/cypress/e2e/smoke/balances.cy.js b/cypress/e2e/smoke/balances.cy.js index bc6d0468b7..9c3a924922 100644 --- a/cypress/e2e/smoke/balances.cy.js +++ b/cypress/e2e/smoke/balances.cy.js @@ -9,7 +9,7 @@ const FIAT_AMOUNT_COLUMN = 2 describe('Assets > Coins', () => { // Fiat balance regex - const fiatRegex = new RegExp(`([0-9]{1,3},)*[0-9]{1,3}.[0-9]{2}`) + const fiatRegex = balances.fiatRegex before(() => { cy.clearLocalStorage() diff --git a/cypress/support/constants.js b/cypress/support/constants.js index 1d2010eb18..ea548667fd 100644 --- a/cypress/support/constants.js +++ b/cypress/support/constants.js @@ -6,6 +6,7 @@ export const SEPOLIA_TEST_SAFE_1 = 'sep:0xBb26E3717172d5000F87DeFd391994f789D80a export const SEPOLIA_TEST_SAFE_2 = 'sep:0x33C4AA5729D91FfB3B87AEf8a324bb6304Fb905c' export const SEPOLIA_TEST_SAFE_3 = 'sep:0x6E834E9D04ad6b26e1525dE1a37BFd9b215f40B7' export const SEPOLIA_TEST_SAFE_4 = 'sep:0x03042B890b99552b60A073F808100517fb148F60' +export const SEPOLIA_TEST_SAFE_5 = 'sep:0xBd69b0a9DC90eB6F9bAc3E4a5875f437348b6415' export const GNO_TEST_SAFE = 'gno:0xB8d760a90a5ed54D3c2b3EFC231277e99188642A' export const PAGINATION_TEST_SAFE = 'gor:0x850493a15914aAC05a821A3FAb973b4598889A7b' export const TEST_SAFE = 'gor:0x04f8b1EA3cBB315b87ced0E32deb5a43cC151a91' @@ -96,6 +97,7 @@ export const transactionStatus = { export const tokenNames = { wrappedEther: 'Wrapped Ether', + sepoliaEther: 'Sepolia Ether', } export const addressBookErrrMsg = {